# Why Scandi

Welcome to Scandi, providing next-generation user experience for e-commerce!

ScandiPWA is a next-generation Magento 2 front-end written in React. It supports [95% of all Magento features](https://manual.scandipwa.com/) while offering a significantly improved user experience and flexible customization technologies. With Scandi, you have the power to use [file overrides](/developing-with-scandi/override-mechanism) for theme development and [application plugins](/developing-with-scandi/extensions) for reusable extensions.

{% hint style="success" %}
You can watch an introductory video [here](https://www.youtube.com/watch?v=xGwvfIAyVrM)!
{% endhint %}

## Client-Side Rendering

In a conventional Magento theme, all of the pages are generated on the server. This means that the client needs to fetch the entire page every time the user clicks on a link, and even with caching enabled, the pages need to be fully re-generated whenever part of the relevant data is modified. Not only does this make Magento resource-hungry, but it results in slow load time.

Instead of rendering the pages on the server and fetching them every time, ScandiPWA only fetches the data from the server (using [GraphQL](https://graphql.org/)), and performs the rendering on the client-side, using [React](https://reactjs.org/). The page is never actually reloaded - once the app's JavaScript code has been loaded, the front-end magic happens without any help from a server. This is called a Single-Page Application, and it provides several advantages.

### Improved Performance

Now that the rendering happens on the client, the server does not need to render the entire page every time. It merely needs to serve the data required by the client, which can happen much faster.

### Smooth Transitions

Since the client is responsible for rendering the page, it knows the structure of the page even before requesting the data. As a result, we can implement a smooth transition to the next page, and display placeholder loaders until the data arrives.

## Progressive Web Application

In addition to being rendered entirely client-side, ScandiPWA is a Progressive Web Application (PWA). This means that it can act similarly to a native iOS or Android application without any additional code.

Being a PWA also means that the application has a service worker (SW). The SW is a piece of caching code that intercepts certain requests it has already seen before, and simply returns the data it has already received, instead of making the same request again. Not only does this make repeat requests almost instantaneous, but it enables offline browsing of data that has already been fetched at some point.

## Still a Magento Theme

Even with all these improvements, ScandiPWA is still a Magento theme - it can be installed on any Magento instance without setting up additional software! The only difference is that we use a faster mechanism for rendering the UI - instead of using Magento's layout system with templates, we use React components.

## Fully Customizable

While the traditional methods of extending a Magento theme won't be applicable, we offer something even better - the [Override Mechanism](/developing-with-scandi/override-mechanism), which you can use to completely customize the theme. Keep any default functionality you need while overriding specific components to suit your needs. There's nothing you can't customize easily with the override mechanism!

In addition, it is possible to create and install [reusable extensions](/developing-with-scandi/extensions) for the theme. With minimal setup investment, you can get additional functionality in your app, defined by an extension – just like in any other Magento store!


# Quick-start Guide

Get familiar with Scandi in a few minutes!

## ☑️ Prerequisites

Make sure you have installed Node v14. We recommend [n](https://www.npmjs.com/package/n) (macOS, Linux) or [nvm-windows](https://github.com/coreybutler/nvm-windows#node-version-manager-nvm-for-windows) to easily manage different Node installations. Also, `npm` should be v6.

Also install the ScandiPWA CLI – it's a command-line tool that will help you work with Scandi much faster:

```
npm i -g scandipwa-cli
```

## 📦 Installation

Let's create a new app so we have something to work with! In the terminal, type:

```bash
npm init scandipwa-app my-first-app
```

This will install all required dependencies, and initialize a new Scandi app in `my-first-app`. Once that has completed, you can take a look at the resulting files:

```
my-first-app/
├── composer.json
├── i18n/
├── yarn.lock
├── magento/
│   ├── etc/
│   ├── registration.php
│   └── theme.xml
├── node_modules/        # Dependencies live here
├── package.json         
├── public/
├── README.md
└── src/                 # 🔍 Your code goes here
```

* `composer.json` and the `magento` directory are needed so that your app can function as a Magento theme
* `package.json` specifies certain information about your app - such as its name and the packages it needs to work
* `yarn.lock` keeps track of the exact dependency versions you have installed. The dependencies themselves live in `node_modules`
* &#x20;`src` and `public` are empty right now - but we will write some code in them!

## 🏃‍♀️ Running the App

We haven't even written any customization code yet, but the Scandi app can work out of the box!

```
cd my-first-app/
npm start
```

The `start` command will run your app at <http://localhost:3000/> and open it in the browser.

{% hint style="info" %}
Note: the app is only visible to you – nobody on the internet can see your Scandi app at the moment. The `npm start` command is for working on your app while testing it on your computer, not for production use.
{% endhint %}

![What you should see](/files/-MZlrTva_uSbjnMFU9ZQ)

{% hint style="info" %}
If you browse around, you will see CMS content and products. But you probably don't have an instance of Magento running on your computer, so where is this data coming from?

By default, `scandipwa-app` is configured to [forward all requests](https://docs.scandipwa.com/dev-environment/proxying-requests-to-server) to a remote Magento instance. There are of course other configurations available, but we'll stick with the proxy for the purposes of this guide – its the fastest to set up.
{% endhint %}

{% hint style="warning" %}
**Known issue:**\
If you come across an *iframe* covering the entire screen when starting ScandiPWA app, please check here: <https://stackoverflow.com/questions/69051008/react-injecting-iframe-with-max-z-index-on-reload-after-changes-development>
{% endhint %}

### 🔍 Quick Tour

How come we got a beautiful working app without writing a single line of code? What you see is the "base" Scandi theme. Its code can be found in `node_modules/@scandipwa/scandipwa/src/` – it is a dependency of your app. We can take a look inside to get an idea of how it works

```
node_modules/@scandipwa/scandipwa/src/
├── component/
├── index.js
├── query/
├── route/
├── service-worker.js
├── style/
├── store/
├── type/
└── util/
```

Here are the most important directories you need to know about:

| Directory   | Purpose                                                                                                                                                                                                                                                                                           |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `component` | <p>This is where all the individual UI components live. For example, there's the Header and Footer components, which are used on all pages, as well as the CheckoutPayment component, which is only used in the checkout payment step.</p><p>Each component has its own UI logic and styling.</p> |
| `route`     | All the different pages of Scandi are defined here. The cart page, the category page, the home page and many others – each has their own folder inside `route`. Their job is to bring the different components together to create entire pages.                                                   |
| `style`     | All the "global" styles that apply to the page as a whole (and are not specific to a single component) go here.                                                                                                                                                                                   |

## &#x20;🎨 Customizing Your App

The app works, but looks just like any other Scandi app. You probably want to customize some of its features. As a first step, let's customize the copyright footer.

Before we can modify its code, we need to know which component is responsible for rendering the Footer. We can use the browser's developer tools to find out. First, inspect the HTML element associated with the footer:

![](/files/-MZvnkwciUN7ZuirhDsJ)

The developer tools should open, giving you information about the element. The element you selected will appear highlighted:

![](/files/-MZvoCrSeOojoB8aOp2u)

Note the class of the element you found. This might be slightly different depending on where you clicked – in our case, it is `Footer-Copyright`. What does this tell us about the component? Because Scandi follows strict [Block-Element-Modifier (BEM)](/structure/building-blocks-summary/components/styling-components#the-bem-methodology) conventions, this gives us the name of the component – `Footer` (the first part of the class, called the Block, is always the same as the component name).

Now that we have found out the name of the component, there is only one place we need to look – the `component` directory in `node_modules/@scandipwa/scandipwa/src/`. Indeed, we can find a component named Footer there:

{% code title="Original File in scandipwa/src/component/Footer/Footer.component.js" %}

```javascript
// [...]
export class Footer extends PureComponent {
    // [...]
    
    renderCopyrightContent() {
        const { copyright } = this.props;

        return (
            <ContentWrapper
              mix={ { block: 'Footer', elem: 'CopyrightContentWrapper' } }
              wrapperMix={ { block: 'Footer', elem: 'CopyrightContent' } }
              label=""
            >
                <span block="Footer" elem="Copyright">
                    { copyright }
                    { ' Powered by ' }
                    <a href="https://scandipwa.com">
                        ScandiPWA
                    </a>
                </span>
            </ContentWrapper>
        );
    }

    render() {
        return (
            <footer block="Footer" aria-label="Footer">
                { this.renderContent() }
                { this.renderCopyrightContent() }
            </footer>
        );
    }
}

export default Footer;
```

{% endcode %}

{% hint style="info" %}
That funny [HTML-in-JavaScript syntax is called JSX](https://reactjs.org/docs/introducing-jsx.html). Scandi uses the React library to render its user interface, and JSX is the easiest way to use React.
{% endhint %}

Now that we have found the component, we can update its code. But **don't** edit the file we found in `node_modules` – modifying dependency code is almost always a bad idea. (It would be hard to get updates, and difficult to track which of the many files you have edited). Instead, let's override it.

### 🎭 Override Mechanism

Scandi offers a great way to customize any component, and its called the Override Mechanism. With the override mechanism, you can override any file you want, while keeping the default implementations for the other files. This gives you great flexibility without having to duplicate any code.

To override a file, you need to create a new file with the same path in your `src` directory. For example...

```
To override:
    component/Footer/Footer.component.js
    (in node_modules/@scandipwa/scandipwa/src/)

...you need to create a new file:
    component/Footer/Footer.component.js
    (in src/)
```

{% hint style="info" %}
Instead of manually creating a new file, you can save yourself a lot of time by using the [Scandi CLI](/developing-with-scandi/developer-tools/scandipwa-cli). With a single command, you can override the Footer component:

```bash
scandipwa override component Footer
```

When you run that command, the Scandi tool will ask you which components you want to override. Select the `Footer` class in `Footer.component.js`; leave the other fields blank:
{% endhint %}

![Using the Scandi CLI to override the Footer component](/files/-MZw0HC0-dlCAzgVAulz)

Now we have created the file, which overrides the original Footer component file. Now, we want to import the original class, so that we can keep most of the Footer functionality, and extend it to customize its behavior:

{% code title="File override in src/component/Footer/Footer.component.js" %}

```javascript
// We will need the ContentWrapper component later - let's import it
import ContentWrapper from 'Component/ContentWrapper';


// Import the original class (we want to keep most of the functionality)
// Note that we are using the "SourceComponent" alias in the import path –
// This tells Scandi that we want to get the original Footer component
import {
    Footer as SourceFooter
} from 'SourceComponent/Footer/Footer.component';


// Extend the original class (SourceFooter)
// By subclassing it, we can change some of its behavior
/** @namespace myFirstApp/Component/Footer/Component/FooterComponent */
export class FooterComponent extends SourceFooter {

    // This is the function responsible for rendering copyright
    // We want to change it, so we re-define in this subclass
    renderCopyrightContent() {
    
        // Changed:
        // Instead of the copyright text, let's write a friendly message
        return (
            <ContentWrapper
              mix={ { block: 'Footer', elem: 'CopyrightContentWrapper' } }
              wrapperMix={ { block: 'Footer', elem: 'CopyrightContent' } }
              label=""
            >
                <span block="Footer" elem="Copyright">
                    Thank you for visiting my website. You are amazing!
                </span>
            </ContentWrapper>
        );
    }
    
    // All the other functions will stay the same...
    // Because we didn't override any other default functionality
}

// All components, including the original Footer component, have a
// default export. Other files use this export when they want to use
// this component.
// Now, instead of providing the original component, we export our
// overridden component. Any file importing this will get the new behavior!
export default FooterComponent;
```

{% endcode %}

### 🔥 Hot Reload

Scandi implements hot reload – which means that you don't need to compile the app again. Just check your browser and the changes should have appeared.

![🎉🎉](/files/-MZvsKkyhkmkbNVc4lY_)

Congratulations! Now you understand the basics of file overriding – and you can override any file in the app to change its behavior.

### 👋 Need Help?

If you get stuck or have any questions, we'd be happy to help! Feel free to drop us a message in [Slack](https://join.slack.com/t/scandipwa/shared_invite/enQtNzE2Mjg1Nzg3MTg5LTQwM2E2NmQ0NmQ2MzliMjVjYjQ1MTFiYWU5ODAyYTYyMGQzNWM3MDhkYzkyZGMxYTJlZWI1N2ExY2Q1MDMwMTk) – all tech-related questions are welcome in the #pwa-tech channel.

## 🚀 Share it With the World!

Now you have your very own Scandi app. Let's deploy so you can brag about it 😂 – all it takes is one command:

```
scandipwa deploy
```

The Scandi CLI will do its magic, and give you the URL of your deployed app. You can check if your updated message is in the Footer – it should still be there, but this time, accessible to anyone on the internet!

![Deploying the app](/files/-M_-Vx90OjuCgPoSG4G2)

## 📍 What Next?

Now that you know the basics of working with Scandi, you can explore the rest of the ecosystem. What you choose to do will depend on your needs and is entirely up to you:

* Want some more hands-on experience? Stop by our [tutorial section](/tutorials/dark-mode-extension)!
* Learn about [extensions](/developing-with-scandi/extensions) – plugins you can easily install to get additional functionality
* Want a working Magento backend to go with your app? Try [create-magento-app](https://docs.create-magento-app.com/)!
* Want to improve your workflow? Check out our [recommended development environment](broken://pages/-MVpv1d26tx-ZTJ79k5l).
* Get a more in-depth understanding of [Scandi's structure](broken://pages/-MVpW4hbz5K-oAQvxyO1)


# Roadmap

<table><thead><tr><th width="150.33333333333334">Timeline</th><th width="403.93503390868085">Plans</th><th>Status</th></tr></thead><tbody><tr><td><strong>Q4 2021</strong></td><td><ul><li>Fix the New Version Popup </li><li>Customer token lifetime - OAuth </li><li>Refactoring of Bundle Products, Customizable Options, Forms, Stock, and Price calculations</li><li>Fixes for iOS 15 </li><li>Radio Button Support </li><li>Date &#x26; Time Custom Options </li><li>My Account refactoring </li><li>Add Items Ordered, Invoices, and Order Shipments tabs and Reorder button in My Orders </li><li>Disable Guest Checkout if Cart Contains Downloadable Items / M2 Support</li></ul></td><td>Complete</td></tr><tr><td><strong>Q1 + Q2 2022</strong></td><td><ul><li>Print Order functionality</li><li>URL parameters to track InApp sessions</li><li>Counter of images in mobile instead of dots </li><li>Hide the bottom navigation bar when scrolling on mobile</li><li>Price Slider extension </li><li>My Orders improvements</li><li>Magento 2.4.4 support</li><li>Start the migration to TypeScript</li></ul></td><td>Complete</td></tr><tr><td><strong>Q3 + Q4 2022</strong></td><td><ul><li>Refactoring of stock</li><li>Refactoring of configurable products</li><li>Refactoring of price and currency</li><li>Migrate to Magento endpoints</li><li>Products per page / M2 Support </li><li>New image zoom for mobile</li><li>New video guides for installing ScandiPWA</li><li>Performance improvements for adding products to the cart</li></ul></td><td>In Progress</td></tr></tbody></table>


# Introduction to the Stack

A quick overview of the technologies of Scandi

ScandiPWA front-end stack basically consists of 4 technologies:

1. [React](https://reactjs.org/docs/getting-started.html) – a JavaScript library for building user interfaces
2. [Redux](https://redux.js.org/) – a global state container and manager
3. ScandiPWA **override mechanism**
4. ScandiPWA **plugin mechanism**

Technologies 1) and 2) are well established in the front-end world, while the 3) and 4) require more attention and explanation.

## React and Redux

Let's first consider how the normal React + Redux application looks like:

<div align="center"><img src="/files/-MVWRrI-E2mPbGM-7Y_D" alt=""></div>

Well, simple! We have one global Redux store, which React Components talk to, and receive updates from. What's the problem? Well, in theory, there is none! If we had to write an application for a single-use – that's would be the way we do it!

But we have a problem. **ScandiPWA is meant to be used as a base for other projects**, it is almost never used without a modification. So, if we consider the above application, what problem would we face?

Well, for every project, **we would have to maintain a copy of an entire application**! Why is that a problem? Well, changes might be located in many different files, **making it hard to trace changed files**. As a consequence: developers might struggle during an update or while locating a file. With 800+ files in the project, going through each and every might be quite a struggle.

So, what might be a solution to this problem? Well, to **make the application contain just the changed files**. This is where the "override mechanism" comes into play!

## Scandi **Override Mechanism**

To make the application contain just the changed files, ScandiPWA introduced the override (aka. shadowing, fallback) mechanism. It works in a very simple way:

{% hint style="info" %}
**To override a file** - means to create a file that will be used instead of the original file.
{% endhint %}

Here is how our new application looks like now (*red highlights application file overrides*):

![](/files/-MVp0c8zr5PNDKNQ_cpT)

The application data-flow is unchanged, and React components and the Redux Store store is now coming from two sources: ScandiPWA and your custom application.

To learn more about how to override, please follow the link below:

{% content-ref url="/pages/-MNcOjtIbzbetYFqebke" %}
[Override Mechanism](/developing-with-scandi/override-mechanism)
{% endcontent-ref %}

Is our application perfect now? Well, yes! At least for 95% of Magento 2 features – that's should be enough! But **what if we need additional functionality, that did not came out-of-the-box**? How do we add it to our application? What if we need this functionality in different combinations on many different projects?

Well, Magento 2 has [its own module-system](/developing-with-scandi/extensions#magento-2-module), but how to deal with such issues in our React + Redux world? Well, there are NPM packages, you might say. Well, **NPM packages solve the issue of distributing and encapsulating the logic of a feature in one place**. But what about their integration? Every time we want to use one, we need to manually import it into our code.&#x20;

Manual import and use of packages might cause many problems. **Feature implementations might require changes to many different places across the application**, tracing this code back after the integration might become very challenging. And **what if our feature requires a change inside one of our NPM packages?** Well, we have no other option, but to copy the package to our project and modify it.

So, what might be a solution to this problem? We need a tool that allows to:

* Build standalone NPM packages
* Contain the integration logic within a distributed package

This is where the "plugin mechanism" comes into play!

## Scandi Plugin Mechanism

To make the NPM packages contain integration logic, ScandiPWA introduced the plugin mechanism (aka. extension mechanism). It works a follows:

{% hint style="info" %}
**To create a plugin** - means to create a programmable proxy between the original function and the function caller. The plugin can modify original function arguments and return values.
{% endhint %}

Here is how our new application looks like now (*blue highlights application plugins*):

![](/files/-MVpIC5O79Xp1PAuUQri)

This is how our application looks in the end. Notice, every layer is encapsulated:

* Plugins are contained in their own packages, there is no need to import them manually. **The code does not get mixed with newly added features.**<br>
* Overrides are contained in their own package and contain project-specific changes. They make it easy to **customize the presentation layer without modifying the source code**.

To learn more about how to create application plugins, please follow the link below:

{% content-ref url="/pages/-MVFUrrqeCkdXcB5fosC" %}
[Extensions](/developing-with-scandi/extensions)
{% endcontent-ref %}


# CMA, CSA, and ScandiPWA

Ecosystem overview

The Scandi technology stack was built to support two modes:

* **Storefront mode** – the statically compiled web application which uses a remote Magento 2 instance as the data source. Usually served by a non-Magento server, thus reducing the costs of maintenance and time-to-first-byte (TTFB).<br>
* **Magento theme mode** –  the statically compiled Magento 2 theme, served by Magento and getting data from the same Magento 2 instance it's hosted on. The main advantages of this approach are better SEO and higher customizability from the admin interface.

To make the development in both modes easier, we introduced multiple toolchains to streamline the setup process:

* **Create Magento App (CMA)** – a toolchain that allows you to setup Magento 2 applications on your computer or server in a single command. <br>
* **Create ScandiPWA App (CSA)** – a toolchain that implements the Override Mechanism, application plugins, and both building modes. It allows creating ScandiPWA applications in a single command.

It is common to install both ScandiPWA and Magento on the local machine to develop efficiently. THe following open-source technologies are made by the ScandiPWA team:

* [Create ScandiPWA App](/#create-scandipwa-app)
* [ScandiPWA theme](/#scandipwa-theme)
* [Create Magento App](/#create-magento-app)

## Create ScandiPWA App

Create ScandiPWA App is an officially supported way to create ScandiPWA applications. It offers a modern build setup with no configuration.

### How does it work?

The core of the CSA is a well-known Create React App - an officially supported way to create single-page React applications.&#x20;

![](/files/-MRZqacFyUmAX6DB8BZj)

With a [CRACO](https://github.com/gsoft-inc/craco) (**C**reate **R**eact **A**pp **C**onfiguration **O**verride) plugin on top, we can use all Create React App features and still be able to customize ESLint, Babel, and other configurations.

![](/files/-MRZqhVpj-NI3pUbni5Q)

[Create ScandiPWA App](https://docs.create-scandipwa-app.com) implements [Override](https://docs.create-scandipwa-app.com/themes/parent-themes) and [Plugin](https://docs.create-scandipwa-app.com/extensions/extensions) mechanisms on top of CRACO. It can be used to develop React applications while enjoying an enhanced development experience.

![](/files/-MRZqre1DplOdNHs5c2I)

## ScandiPWA Theme

Alongside a new way of building your React applications, we created an open-source PWA theme for Magento 2.

[ScandiPWA](https://scandipwa.com/) theme is enabling you to build a faster, smoother, and offline-enabled experience for your customers, boosting conversion rates. It is built on Progressive Web App (PWA) technology, which is increasingly favored by companies such as Amazon, [Alibaba](https://developers.google.com/web/showcase/2016/alibaba#results) and Uber due to its UX improvements.

![](/files/-MS2UE4dggjpAboefS7U)

The ScandiPWA theme is fully customizable. It introduces a new PWA front-end for Magento 2 alongside the back-end Magento modules with GraphQL endpoints needed to cover all Magento 2 features.

## Create Magento App

When it comes to the back-end development, we introduce you the [Create Magento App](https://docs.create-magento-app.com/) - the fastest way of setting up Magento locally. This deployment technology combines two powerful approaches of containerization and Infrastructure as code to provision you a Magento 2 instance in minutes.

With just one command line, you can [link](https://docs.create-magento-app.com/usage-guide/themes) your Create Magento App together with your Create ScandiPWA App, and build your Magento 2 PWA store locally.

![](/files/-MS2U-QD32xHl7knTcGJ)


# Challenges

The challenges of creating a PWA for Magento and how we overcome them

## SEO

Search Engine Optimization (SEO) is important to us, as it drives organic search discoverability and sales. It involves making sure that search engines can crawl the application to index its content.

Traditionally, crawlers are not built to handle Client-Side Rendered (CSR) applications. CSR happens entirely in JavaScript, and most crawlers don't run JavaScript at all - therefore, they will be unable to see the application's content unless we take this into consideration.

While some crawlers may now be able to run JavaScript, we still need to ensure that all search engines can index our app. This can be achieved with several different approaches, all of which involve rendering part of the app on the server, and responding with that result.

[Dynamic rendering](https://developers.google.com/search/docs/guides/dynamic-rendering): the web server needs to detect crawlers. For crawlers, it serves a pre-rendered version of the app, but for other clients, it serves the regular JavaScript app.

{% hint style="info" %}
Pre-rendering: the frontend App is run on the server, in a headless browser. The resulting HTML is served instead of the app.
{% endhint %}

## &#x20;API Complexity

The traditional style of building APIs is [REST](https://restfulapi.net/). It involves creating multiple endpoints that the client can use to fetch and manipulate resources. However, for a large application such as Magento, the REST approach starts encountering problems:

* Increasing numbers of endpoints are hard to keep track of and remember
* Additional data returned by each endpoint (sometimes unnecessarily) grows the size of the response

To address these problems, Magento also supports a [GraphQL](https://graphql.org/) API. ScandiPWA builds on the GraphQL API and uses it in the frontend app, due to its advantages:

* GraphQL only offers 1 endpoint with well-documented queries
* The client can now request only the data it needs, saving bandwidth
* Endpoints and their datatypes are documented when defining the schema, which is checked by GraphQL

### GraphQL Caching

Using GraphQL creates an additional challenge - we need to find a way to cache API requests, which could easily be done with REST. By default, Magento sends the full query as a stringified JSON parameter, to take advantage of `GET` request caching. Despite this method's simplicity, it creates a limitation in query size, as the maximum URL length cannot be exceeded. In addition, this strategy requires the full query to be sent every time, increasing bandwidth usage.&#x20;

ScandiPWA uses an alternative approach - the persisted query method. Initially introduced by [Apollo](https://www.apollographql.com/), this approach involves transforming each query into a short identifier, which solves the caching problem and saves bandwidth, at the cost of some additional requests made.


# Setting up Scandi

Learn about different installation modes

ScandiPWA recommends developing applications locally. We have created two tools in order to make this process of installation smooth. They are as follows:

| Use...                                                                                          | ...if you need:           |
| ----------------------------------------------------------------------------------------------- | ------------------------- |
| [`create-scandipwa-app`](https://docs.create-scandipwa-app.com/getting-started/getting-started) | A **ScandiPWA front-end** |
| [`create-magento-app`](https://docs.create-magento-app.com/getting-started/getting-started)     | A **Magento back-end**    |

It is possible to combine these setups to deploy ScandiPWA in multiple modes:

* [Storefront mode (CSA)](/getting-started-1#storefront-mode-csa)
* [Magento theme mode (CSA + CMA)](/getting-started-1#magento-theme-mode-csa-cma)

{% hint style="danger" %}
If you are **deploying ScandiPWA on a production server** or you would like to **develop on a remote server** (via SSH, for example), please follow the link below.
{% endhint %}

{% content-ref url="/pages/-MP3PlnAtlis8uSWi5pe" %}
[Existing Magento 2 setup](/getting-started-1/magento-integration)
{% endcontent-ref %}

## **Storefront mode (CSA)**

In Standalone storefront mode - the **front-end is running on a small separate server** and uses a remote Magento 2 instance as the source for the data.

{% hint style="info" %}
In this mode, **you are not required to run Magento 2 locally** to develop.
{% endhint %}

When using this mode it is enough to have a ScandiPWA theme powered by **CSA (Create ScandiPWA App)** to compile it. As a data source, you can use any existing [properly configured](/getting-started-1/storefront-mode#configuring-the-magento-server) Magento 2 instance or create a ready Magento 2 instance using [RMG (ReadyMage)](https://readymage.com).

{% content-ref url="/pages/-MUxW5d\_bCOq1\_Inrj1D" %}
[Storefront Mode Setup](/getting-started-1/storefront-mode)
{% endcontent-ref %}

## Magento theme mode (CSA + CMA)

In Magento 2 theme mode - the front-end is statically compiled and **served by Magento 2 server**. In this case, data comes directly from the same server the application is hosted on.

{% hint style="info" %}
In this mode, **you are required to run Magento 2 locally** to develop.
{% endhint %}

When developing a Magento 2 theme, the local installation of Magento 2 is required. In order to simplify the local Magento 2 installation, we created **CMA (Create Magento App)** toolchain, with a goal to start a Magento 2 instance with a single command. The **CSA (Create ScandiPWA App)** toolchain will still be used to compile a theme.

{% content-ref url="/pages/-MUxW7Lyak4clRkIK5\_U" %}
[Magento Mode Setup](/getting-started-1/magento-theme-mode)
{% endcontent-ref %}


# Storefront Mode Setup

Run Scandi as an separate app

## Summary

* [ ] Ensure you have Node v12 or newer on your development machine
* [ ] `npx create-scandipwa-app my-app` to create a new app
* [ ] `cd my-app` to enter your app's directory
* [ ] `npm start` to run the app

These commands will create a new Scandi app, start it up, and open it in your browser.

## Creating a ScandiPWA App

You’ll need to have Node >= 12 on your local development machine (but it’s not required on the server). You can use [n](https://www.npmjs.com/package/n) (macOS, Linux) or [nvm-windows](https://github.com/coreybutler/nvm-windows#node-version-manager-nvm-for-windows) to switch Node versions between different projects.

To create a new app, you may choose one of the following methods:

{% tabs %}
{% tab title="NPX" %}

```bash
npx create-scandipwa-app my-app
```

{% endtab %}

{% tab title="NPM" %}

```
npm init scandipwa-app my-app
```

{% endtab %}

{% tab title="Yarn" %}

```
yarn create scandipwa-app my-app
```

{% endtab %}
{% endtabs %}

## Output

Running any of these commands will create a directory called `my-app` inside the current folder. Inside that directory, it will generate the initial project structure and install the transitive dependencies. More details:

{% content-ref url="/pages/-MUxZ3slqBE7329uexmH" %}
[Directory Structure](/structure/folder-structure)
{% endcontent-ref %}

## Available Commands

Inside the newly created project, you can run some [built-in commands](/developing-with-scandi/developer-tools/available-commands):

### `npm start` or `yarn start`

Runs the app in development mode. Will open the [http://localhost:3000](http://localhost:3000/) to preview changes in your default browser.\
\
The page will automatically reload if you make changes to the code. You will see the build errors and lint warnings in the console.

### `npm run build` or `yarn build`

Builds the app for production to the `build` folder. It correctly bundles React in production mode and optimizes the build for the best performance.

The build is minified and the filenames include the hashes.Your app is ready to be deployed.

## Connecting to a Magento server

By default, your new application will be fetching data from a remote store. If you want to use your own Magento instance, you can either create a new **CMA** ([by following this guide](https://docs.create-magento-app.com/getting-started/getting-started)) or set up a Magento instance manually. After that, you must configure it to include ScandiPWA-specific modules ([as described here](/getting-started-1/storefront-mode#configuring-the-magento-server)).

## Configuring the Magento server

To use Magento 2 as a data source for ScandiPWA, you are required to make sure that it is using the correct Composer dependencies. The list of your application Composer dependencies can be found in your ScandiPWA application's `composer.json` file.

You can copy the dependencies defined in `require` field of your application's `composer.json` to your Magento server's root `composer.json`  and execute the `composer update` command.


# Proxying requests to server

To tell the development server to proxy requests to your Magento 2 server, modify a `proxy` field to your `package.json`, for example:&#x20;

```javascript
"proxy": "http://localhost:4000",
```

{% hint style="warning" %}

### Heads up!

This feature is only supported in `development` (when using [`start` command](/developing-with-scandi/developer-tools/available-commands#npm-start-or-yarn-start)).
{% endhint %}

## Configuring proxy manually

If the `proxy` option is **not** flexible enough for you, you can get direct access to the Express app instance and hook up your own proxy middleware.

You can use this feature in conjunction with the `proxy` property in `package.json`, but it is recommended you consolidate all of your logic into `src/setupProxy.js`.

First, install `http-proxy-middleware` using npm or Yarn:

```bash
npm install http-proxy-middleware --save # for NPM
yarn add http-proxy-middleware # for Yarn
```

Next, create `src/setupProxy.js` and place the following contents in it:

```javascript
const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
  // ...
};
```

You can now register proxies as you wish! Here's an example using the above `http-proxy-middleware`:

```javascript
const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
  app.use(
    '/graphql',
    createProxyMiddleware({
      target: 'http://localhost:5000',
      changeOrigin: true,
    })
  );
};

```

{% hint style="warning" %}

### Heads up!

You do not need to import this file anywhere. It is automatically registered when you start the development server.
{% endhint %}

## Configuring Magento 2 server

To use Magento 2 as a data source for ScandiPWA, you are required to make sure that it is using the correct Composer dependencies. The list of your application Composer dependencies can be found in your ScandiPWA application's `composer.json` file.

You can copy the dependencies defined in `require` field of your application's `composer.json` to your Magento server's root `composer.json`  and execute the `composer update` command.


# Magento Mode Setup

Build Scandi as a Magento theme

This guide requires 5 steps to be completed in sequence:

1. [Create a new Create Magento App](/getting-started-1/magento-theme-mode#create-a-new-create-magento-app)
2. [Create a new Create Scandipwa App](/getting-started-1/magento-theme-mode#create-a-new-create-scandipwa-app)
3. [Link Magento App with ScandiPWA App](/getting-started-1/magento-theme-mode#link-magento-app-with-scandipwa-app)
4. [Run ScandiPWA App in Magento mode](/getting-started-1/magento-theme-mode#4-run-scandipwa-app-in-magento-mode)
5. [Change Magento theme to ScandiPWA](/getting-started-1/magento-theme-mode#5-change-magento-theme-to-scandipwa)

## 1. Create a new Create Magento App

### Install dependencies

Before setting up, make sure you have installed the **libraries required to build the PHP**. The list of these libraries can be found here: for [Linux](https://docs.create-magento-app.com/getting-started/prerequisites/installation-on-linux) and [macOS](https://docs.create-magento-app.com/getting-started/prerequisites/installation-on-macos).

Also, make sure to install **Docker** and **PHPBrew** in your system.

### Quick Start

```bash
# make sure dependencies are installed !!!
npx create-magento-app my-app
cd my-app
npm start
```

This command will start Docker services, start PHP and open your favorite browser with Magento 2 store.

Create Magento App choose an available port for Magento 2 so it can vary. By default, it will use port 80 so the URL for the store will be [http://localhost:80/](http://localhost/).

### Creating a Magento App

{% hint style="warning" %}

### **Make sure your Node version is up to date!**

**You’ll need to have Node >= 12 on your local development machine** (but it’s not required on the server). You can use [n](https://www.npmjs.com/package/n) (macOS, Linux) or [nvm-windows](https://github.com/coreybutler/nvm-windows#node-version-manager-nvm-for-windows) to switch Node versions between different projects.
{% endhint %}

To create a new app, you may choose one of the following methods:

{% tabs %}
{% tab title="NPX" %}

```bash
npx create-magento-app my-app
```

{% endtab %}

{% tab title="NPM" %}

```
npm init magento-app my-app
```

{% endtab %}

{% tab title="Yarn" %}

```
yarn create magento-app my-app
```

{% endtab %}
{% endtabs %}

### Output

Running any of these commands will create a directory called `my-app` inside the current folder. Inside that directory, it will generate the initial project structure and install the transitive dependencies. Learn more in [the folder structure guide](https://docs.create-magento-app.com/getting-started/folder-structure).

{% hint style="info" %}
**In case of issues** - please refer to [Create Magento App FAQ.](https://docs.create-magento-app.com/troubleshooting/common-issues)
{% endhint %}

## 2. Create a new Create Scandipwa App

The ScandiPWA App creation very similar to Storefront mode, however, **the Magento 2 server configuration is not required** as we will install our ScandiPWA as composer dependency for our newly created Magento 2 server, later in this guide.

{% content-ref url="/pages/-MUxW5d\_bCOq1\_Inrj1D" %}
[Storefront Mode Setup](/getting-started-1/storefront-mode)
{% endcontent-ref %}

## 3. Link Magento App with ScandiPWA App

The Create Magento App (CMA) comes with a built-in mechanism for ScandiPWA theme linking. In order to link a ScandiPWA theme with CMA project, execute the following command from CMA project root:

{% tabs %}
{% tab title="NPM" %}

```bash
npm run link -- ./path/to/your/scandipwa-app
```

{% endtab %}

{% tab title="Yarn" %}

```
yarn run link ./path/to/your/scandipwa-app
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
This command will link your ScandiPWA theme from your selected path as a symbolic link and will [disable the "Full Page" cache](https://docs.magento.com/user-guide/system/cache-full-page.html).
{% endhint %}

## 4. Run ScandiPWA App in Magento mode

Run the command below from your ScandiPWA App directory:

{% tabs %}
{% tab title="Mac, Linux" %}

```bash
BUILD_MODE=magento npm run start
```

{% endtab %}

{% tab title="Windows" %}

```bash
set BUILD_MODE=magento && npm run start & set BUILD_MODE=
```

{% endtab %}
{% endtabs %}

This command will now watch the files and put their compiled versions into the `magento/Magento_Theme` folder.

## 5. Change Magento theme to ScandiPWA

Go to your Magento Admin panel (by default it can be accessed on `/admin`), **Content > Configuration**, choose a website that you want to apply theme on, click **Edit** and select your theme, click **Save**!

Open your store URL and the ScandiPWA theme should be online.

## What's next?

Learn what features and why we added on-top-of React + Redux stack:

{% content-ref url="/pages/-MVVgnQHeo0DBRZnPZN8" %}
[Introduction to the Stack](/introduction)
{% endcontent-ref %}


# Existing Magento 2 setup

The ScandiPWA theme integrates seamlessly with Magento

You might want to install ScandiPWA as a Magento theme (without using [Create Magento App](http://docs.create-magento-app.com/)) – and this is supported out of the box! You can install the theme as a local composer module, continuously build it, and Magento will be able to recognize it as a valid Magento theme. Then, you will be able to select your ScandiPWA-based theme in the Magento configuration.

{% hint style="info" %}
If you are using[ Create Magento App ](https://scandipwa.gitbook.io/create-magento-app/)you're lucky! It has a built-in ScandiPWA linking function built-in. Read how to [setup ScandPWA with CMA](/getting-started-1/magento-theme-mode) in minutes!
{% endhint %}

## Prerequisites

Make sure that you have a [supported Magento version](https://manual.scandipwa.com/pwa/magento-version-mapping)! Also, make sure your server is configured to point into `<MAGENTO ROOT>/pub` directory.

#### Node v14+

```bash
node -v # should be 14^
```

You can instal Node using [`nvm`](https://github.com/nvm-sh/nvm) (recommended) or the [official guide](https://nodejs.org/en/download/package-manager/).

#### Varnish v5+

```bash
varnishd -V # should be 5^
```

In Magento admin go to *Stores > Configuration > Advanced > System > Full Page Cache*. Make sure the `Varnish Cache` is selected in the dropdown, varnish configuration has proper values set in it.

If it is not, please follow [official documentation](https://devdocs.magento.com/guides/v2.3/config-guide/varnish/config-varnish.html) to set it up.

#### Redis v2.5+

```bash
redis-cli -v # should output 2.5^
```

If it is not installed, please follow [this guide](https://codewithhugo.com/install-just-redis-cli-on-ubuntu-debian-jessie/) to obtain it.

## Install the ScandiPWA theme with Composer

We recommend you keep your theme source in a `src/localmodules` directory. You will then be able to configure composer to install the theme from here as a local module.

```bash
mkdir src/localmodules
cd src/localmodules
```

{% tabs %}
{% tab title="If you're just getting started" %}
Verify that your system has the [required tools](/getting-started-1#prerequisites) installed, and [create a new theme](/getting-started-1#installation)!
{% endtab %}

{% tab title="If you already have a theme" %}
Simply move your ScandiPWA theme into the `localmodules` directory:

```bash
mv ~/Projects/<your-theme> . # path to your theme
cd <your-theme>
```

{% endtab %}
{% endtabs %}

Build or start the application in Magento mode:

{% tabs %}
{% tab title="yarn (recommended)" %}

```bash
BUILD_MODE=magento yarn start # or build
```

{% endtab %}

{% tab title="npm" %}

```bash
BUILD_MODE=magento npm start # or build
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
I**f you chose `start` – you will need to keep this process running**. It continuously re-builds the theme when changes are made. Open a new terminal tab to enter new commands.
{% endhint %}

Now the new theme is created, but we need to install it using Composer. We will install the newly-created theme by taking advantage of Composer's ability to install from [local repository sources](https://getcomposer.org/doc/05-repositories.md#path).

First, we add our theme as a local repository source. This will alter `composer.json` to add a new item in the `repositories` field:

```bash
composer config repo.theme path localmodules/<your-app-name>
```

Next, we install our theme by using `require`. This will resolve the package to the localmodules directory we configured above:

```bash
composer require scandipwa/<your-app-name>
```

## Configure persisted-query

For improved ScandiPWA query caching to work, you must configure `scandipwa/persisted-query`. For convenience, there are additional flags available for `php bin/magento setup:config:set` command:

| Flag            | Required? | Description                                                              | Example     |
| --------------- | --------- | ------------------------------------------------------------------------ | ----------- |
| `--pq-host`     | Yes       | Persisted query Redis host                                               | `127.0.0.1` |
| `--pq-port`     | Yes       | Persisted query Redis port                                               | `6379`      |
| `--pq-database` | Yes       | Persisted query Redis database                                           | `5`         |
| `--pq-scheme`   | Yes       | Persisted query Redis database                                           | `tcp`       |
| `--pq-password` | No        | <p>Persisted query Redis password<br>(empty password is not allowed)</p> | *empty*     |

## Enable the ScandiPWA Theme

Run the `upgrade` command and disable full-page caching:

```bash
bin/magento setup:upgrade
bin/magento cache:disable full_page
```

It is now time to enable the new theme. In the Magento admin panel, navigate to **Content** > **Design** > **Configuration**. Edit the scope you want to change (typically the most general one in the list), and select the new theme. Finally, flush the cache:

```bash
bin/magento cache:flush
```

{% hint style="success" %}
The new theme should now be served on the frontend. Congratulations, you now have a ScandiPWA Magento Theme!
{% endhint %}


# Magento Commerce Cloud setup

{% hint style="info" %}
Alternatively to Magento Commerce Cloud consider using [ReadyMage](https://readymage.com/). ReadyMage is ScandiPWA optimized cloud hosting and supports Magento Commerce projects.
{% endhint %}

ScandiPWA setup on Magento Commerce Cloud requires adjusting 2 files in the project root folder that are used for deployment flow.

## bitbucket-pipelines.yaml

Requires adjustments to allow pushing code from BitBucket of the git repository to Magento Cloud project repository. CI/CD is used for this purpose. \
Git authentication is made using public keys. Generate the key in the BitBucket repository and add it to the Magento Cloud in the admin panel.

Here is an example of how the `bitbucket-pipelines.yaml` file should be configured. It contains configuration for 2 environments:

* production (using master branch);
* stage (using stage branch).

{% hint style="info" %}
Replace example credentials and git links with your project actual links.
{% endhint %}

```
pipelines:
  branches:
    master:
      - step:
          image: luzhzh/git-client
          script:
            - set -e
            - set -o pipefail
            - git clone --branch master git@bitbucket.org:exampleorganization/exampleproject.git exampleproject
            - cd exampleproject/
            - git config --global user.email "exampleemail@example.com"
            - git config --global user.name "Deploy Script"
            - git remote add mc example@git.eu-5.magento.cloud:example.git
            - git pull --no-edit mc master
            - git push mc master
            - cd ../
            - rm -rf exampleproject.git/
    stage:
      - step:
          image: luzhzh/git-client
          script:
            - set -e
            - set -o pipefail
            - git clone --branch stage git@bitbucket.org:exampleorganization/exampleproject.git exampleproject
            - cd exampleproject/
            - git config --global user.email "exampleemail@example.com"
            - git config --global user.name "Deploy Script"
            - git remote add mc example@git.eu-5.magento.cloud:example.git
            - git pull --no-edit mc stage
            - git push mc stage
            - cd ../
            - rm -rf exampleproject.git/
definitions:
  caches:
    node-custom: app/design/frontend/ExampleProject/pwa/node_modules
```

## magento.app.yaml

ScandiPWA compilation is done in `magento.app.yaml` file. Within it options and commands are configured to be run during deployment.

Here is part of the file that is responsible for ScandiPWA compilation which has to be added to the existing `magento.app.yaml` file content.

```
hooks:
    # We run build hooks before your application has been packaged.
    build: |
        set -e

        git apply m2-patches/*.patch

        unset NPM_CONFIG_PREFIX
        curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | dash
        export NVM_DIR="$HOME/.nvm"
        [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
        nvm current
        nvm install 12.16.1

        (cd ./app/design/frontend/ExampleProject/pwa/ && rm -rf node_modules && npm ci)
        (cd ./app/design/frontend/ExampleProject/pwa/ && npm run build)

        php ./vendor/bin/ece-tools run scenario/build/generate.xml
        php ./vendor/bin/ece-tools run scenario/build/transfer.xml
    # We run deploy hook after your application has been deployed and started.
    deploy: |
        php ./vendor/bin/ece-tools run scenario/deploy.xml
    # We run post deploy hook to clean and warm the cache. Available with ECE-Tools 2002.0.10.
    post_deploy: |
        php ./vendor/bin/ece-tools run scenario/post-deploy.xml
```


# Updating to new releases

Update your Scandi app to the latest version

Before you start, make sure your project is backed up, ideally in a version control system such as Git. You also need to check that the Magento and Scandi versions you are updating to are [compatible](https://manual.scandipwa.com/pwa/magento-version-mapping) with each other.

## Update Create ScandiPWA App

Sometimes, we release new features of our toolchain, like a new way to write plugins, or some patches to support newer OS versions. To upgrade the toolhain, use this guide:

{% content-ref url="/pages/-MV0PXvo5UYb1Ij7LKNd" %}
[CSA upgrade](/getting-started-1/updating-scandipwa/csa-upgrade)
{% endcontent-ref %}

## Update in ScandiPWA Storefront mode

To upgrade the ScandiPWA in Storefront mode, you must upgrade the theme and the remote server composer.json. To learn how to do it, follow the guide:

{% content-ref url="/pages/-MUxW5d\_bCOq1\_Inrj1D" %}
[Storefront Mode Setup](/getting-started-1/storefront-mode)
{% endcontent-ref %}

## Update in ScandiPWA Magento theme mode

Upgrade ScandiPWA which is installed as a Magento 2 theme.

{% content-ref url="/pages/-MV0PPaNOJ\_h0e\_lD-m\_" %}
[Magento mode upgrade](/getting-started-1/updating-scandipwa/magento-mode-upgrade)
{% endcontent-ref %}

## Update in ScandiPWA Storefront mode

Upgrade ScandiPWA which is installed as a Storefront, upgrade it's dependencies on remote Magento 2 server.

{% content-ref url="/pages/-MUxW5d\_bCOq1\_Inrj1D" %}
[Storefront Mode Setup](/getting-started-1/storefront-mode)
{% endcontent-ref %}


# Storefront mode upgrade

Upgrade Scandi in Storefront theme mode to the newest version

The following steps must be completed to update to the newer ScandiPWA version:

1. [Update the version of `@scandipwa/scandipwa` ](/getting-started-1/updating-scandipwa/storefront-mode-upgrade#1-update-the-version-of-scandipwa-scandipwa)
2. [Update the `composer.json` dependencies](/getting-started-1/updating-scandipwa/storefront-mode-upgrade#2-update-the-composer-json-dependencies)
3. [Update the composer on the remote Magento server](/getting-started-1/updating-scandipwa/storefront-mode-upgrade#3-update-the-composer-on-the-remote-magento-server)

### 1. Update the version of `@scandipwa/scandipwa`&#x20;

Go to your ScandiPWA theme root directory and run the following:

{% tabs %}
{% tab title="Yarn" %}

```bash
yarn add @scandipwa/scandipwa@latest
```

{% endtab %}

{% tab title="NPM" %}

```
npm install @scandipwa/scandipwa@latest
```

{% endtab %}
{% endtabs %}

### 2. Update the `composer.json` dependencies

To update the Composer dependencies of your ScandiPWA theme, first, build or start the application:

{% hint style="warning" %}

### Heads up!

When upgrading, the build or start of the application might throw an error asking you to upgrade the `composer.json`! **This is expected.**
{% endhint %}

{% tabs %}
{% tab title="Yarn" %}

```bash
yarn start
```

{% endtab %}

{% tab title="NPM" %}

```
npm run start
```

{% endtab %}
{% endtabs %}

Complete the instructions indicated in error, for example following error:

![](/files/-MV0S24Co-_Np3a4mscy)

Must indicate, that you must bump the `scandipwa/customer-graph-ql` to a newer version (`^3`). You can do it in `composer.json`.

{% hint style="info" %}
Another way to quickly resolve this is to copy the `require` field of your theme's parent theme. This is, however, not safe as it might omit some extension's dependencies.
{% endhint %}

### 3. Update the composer on the remote Magento server

Update the `composer.json` on your Magento 2 server. You can again synchronize the updates from ScandiPWA theme's `require` field of `composer.json` and your Magento 2 server's root `composer.json`.

## Upgrade the CSA and CMA

To make sure all the features of the new ScandiPWA are working correctly, make sure to stay on the latest toolchains. Follow the guides below to upgrade them:

{% content-ref url="/pages/-MV0PVbl\_68pDcp4M0Up" %}
[CMA upgrade](/getting-started-1/updating-scandipwa/cma-upgrade)
{% endcontent-ref %}

{% content-ref url="/pages/-MV0PXvo5UYb1Ij7LKNd" %}
[CSA upgrade](/getting-started-1/updating-scandipwa/csa-upgrade)
{% endcontent-ref %}


# Magento mode upgrade

Upgrade ScandiPWA in Magento theme mode to the newer version

The following steps must be completed to update to the newer ScandiPWA version:

1. [Update the version of `@scandipwa/scandipwa` ](/getting-started-1/updating-scandipwa/magento-mode-upgrade#1-update-the-version-of-scandipwa-scandipwa)
2. [Update the `composer.json` dependencies](/getting-started-1/updating-scandipwa/magento-mode-upgrade#2-update-the-composer-json-dependencies)
3. [Update the Magento 2 dependencies](/getting-started-1/updating-scandipwa/magento-mode-upgrade#3-update-the-magento-2-dependencies)

### 1. Update the version of `@scandipwa/scandipwa`&#x20;

Go to your ScandiPWA theme root directory and run the following:

{% tabs %}
{% tab title="Yarn" %}

```bash
yarn add @scandipwa/scandipwa@latest
```

{% endtab %}

{% tab title="NPM" %}

```
npm install @scandipwa/scandipwa@latest
```

{% endtab %}
{% endtabs %}

### 2. Update the `composer.json` dependencies

To update the Composer dependencies of your ScandiPWA theme, first, build or start the application:

{% hint style="warning" %}

### Heads up!

When upgrading, the build or start of the application might throw an error asking you to upgrade the `composer.json`! **This is expected.**
{% endhint %}

{% tabs %}
{% tab title="Yarn" %}

```bash
yarn start
```

{% endtab %}

{% tab title="NPM" %}

```
npm run start
```

{% endtab %}
{% endtabs %}

Complete the instructions indicated in error, for example following error:

![](/files/-MV0S24Co-_Np3a4mscy)

Must indicate, that you must bump the `scandipwa/customer-graph-ql` to a newer version (`^3`). You can do it in `composer.json`.

{% hint style="info" %}
Another way to quickly resolve this is to copy the `require` field of your theme's parent theme. This is, however, not safe as it might omit some extension's dependencies.
{% endhint %}

### 3. Update the Magento 2 dependencies

{% hint style="warning" %}

### Heads up!

**It might be required to delete `composer.lock` file and`vendor` directory** to ensure the changes in ScandiPWA's theme composer will be respected.
{% endhint %}

Go to your Magento 2 root directory and run the following:

```
composer update
```

## Upgrade the CSA and CMA

To make sure all the features of the new ScandiPWA are working correctly, make sure to stay on the latest toolchains. Follow the guides below to upgrade them:

{% content-ref url="/pages/-MV0PXvo5UYb1Ij7LKNd" %}
[CSA upgrade](/getting-started-1/updating-scandipwa/csa-upgrade)
{% endcontent-ref %}

{% content-ref url="/pages/-MV0PVbl\_68pDcp4M0Up" %}
[CMA upgrade](/getting-started-1/updating-scandipwa/cma-upgrade)
{% endcontent-ref %}


# CMA upgrade

From the CMA project root, bump the version of `@scandipwa/magento-scripts` to the latest release and install it:

{% tabs %}
{% tab title="yarn (recommended)" %}

```bash
yarn add @scandipwa/magento-scripts@latest
```

{% endtab %}

{% tab title="npm" %}

```
npm install @scandipwa/magento-scripts@latest
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
This will update the packages of [Create Magento App](https://docs.create-magento-app.com/). Find more information [here](https://docs.create-magento-app.com/getting-started/updating-to-new-releases).
{% endhint %}

We commit to keeping the breaking changes to a minimum. However, you should check if there are any migration instructions in the [changelog](https://github.com/scandipwa/create-magento-app) and follow them to adapt to a new version.

Restart the compilation script and verify that everything is working as expected.


# CSA upgrade

From the CSA project root, bump the version of `@scandipwa/scandipwa-scripts` to the latest release and install it:

{% tabs %}
{% tab title="yarn (recommended)" %}

```bash
yarn add @scandipwa/scandipwa-scripts@latest
```

{% endtab %}

{% tab title="npm" %}

```
npm install @scandipwa/scandipwa-scripts@latest
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
This will update the packages of [Create ScandiPWA App](https://docs.create-scandipwa-app.com/.). Find more information [here](https://docs.create-scandipwa-app.com/getting-started/updating-to-new-releases).
{% endhint %}

We commit to keeping the breaking changes to a minimum. However, you should check if there are any migration instructions in the [changelog](https://github.com/scandipwa/create-scandipwa-app/releases) and follow them to adapt to a new version.

Restart the compilation script and verify that everything is working as expected.


# Custom ScandiPWA composer dependency update

Upgrade a custom ScandiPWA module in Magento theme mode to the newer version

There may be an issue when the composer is installing a newer package version than it's required. The newer package can contain the breaking changes that causes project crashes.\
In order to prevent this, ScandiPWA theme should be up to date. But for some cases this is not possible.\
Thus ScandiPWA has strict composer versions for the `scandipwa/*` modules.

{% hint style="warning" %}

### Heads up!

Updating all ScandiPWA modules using `composer update "scandipwa/*"` may lead to the issue described above. Use it on your own risk!
{% endhint %}

The following steps must be completed to update a custom ScandiPWA module to the specific version:

1. [Edit `composer.json`](/getting-started-1/updating-scandipwa/custom-scandipwa-composer-dependency-update#1-edit-composer-json)
2. [Update ScandiPWA composer dependencies](/getting-started-1/updating-scandipwa/custom-scandipwa-composer-dependency-update#2-update-scandipwa-composer-dependencies)

### 1. Edit `composer.json`

Open a `composer.json` file in your theme directory and set desired version for packages you need. You might want to update multiple packages at once.

### 2. Update ScandiPWA composer dependencies

In order to update ScandiPWA composer dependencies you have to run the `composer update` command with all packages you trying to update.\
For example, if you edited `composer.json` and set new versions to `scandipwa/catalog-graphql` and `scandipwa/compare-graphql`, the command would be the following:

```bash
composer update scandipwa/catalog-graphql scandipwa/compare-graphql
```

{% hint style="warning" %}

### Heads up!

You cannot update a ScandiPWA composer dependency directly, without modifying the `composer.json` file.\
If you try this, you will get the error like this:
{% endhint %}

```bash
Only root package requirements can receive temporary constraints
and scandipwa/quote-graphql is not one
```

{% hint style="warning" %}

### Heads up!

If you don't mention all updated packages in the update command, you will get the following error:
{% endhint %}

```
Your requirements could not be resolved to an installable set of packages.
```


# Local ScandiPWA Composer Package Setup

ScandiPWA Composer package installation and upgrade for the Core Team

1. [Why](/getting-started-1/updating-scandipwa/local-scandipwa-composer-package-setup#why)
2. [How to install](/getting-started-1/updating-scandipwa/local-scandipwa-composer-package-setup#how-to-install)
3. [How to update](/getting-started-1/updating-scandipwa/local-scandipwa-composer-package-setup#how-to-update)
4. [Version configuration](/getting-started-1/updating-scandipwa/local-scandipwa-composer-package-setup#version-configuration)

### Why

Composer always is trying to install the latest possible version of a package. This means, that if your `composer.json` file has packages with version `^2.1.0`, it matches `2.1.1` and `2.1.99`.

But since ScandiPWA releases are backward incompatible and during the release cycle we can have multiple backend module releases, this can lead to an issue when a new ScandiPWA setup can install latest composer package that will crash the project.

To solve this issue a developer have to install the latest ScandiPWA frontend. But it may not be possible since it isn't released yet, or for some other reasons. Thus we decided to use strict composer package versions in order to lock changes for a specific ScandiPWA release.

But this leads to another issue for a ScandiPWA Core Team. Since we are developing composer packages locally, we used to specify a local package version to be greater than it is in the `packagist.org`. So the Composer would prefer the local version over the Packagist version. This will not happen when we lock the versions.

The solution for the Core Team is to use branch alias.

### How To Install

In order to install the local package, first we should configure repository:

```
composer config repo.repo-name path path/to/repo
```

Then we should lookup the required version of the package in the theme's `composer.json`. Next is to launch the following command:

```
composer require "vendor/package dev-master as x.x.x"
```

For example, in order to install the `scandipwa/catalog-graphql` version `3.1.24` do the following:

```
composer require "scandipwa/catalog-graphql dev-master as 3.1.24"
```

This will add a package to the root `composer.json` and symlink the package in the `vendor` directory.

### How To Update

In order to update ScandiPWA composer packages the Core Team can use the following command:

```
composer update "scandipwa/*"
```

But if a package, that is installed from local directory, has updated in the theme's `composer.json`, you will have an error like this:

```
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - Root composer.json requires scandipwa/scandipwa ^0.0.1 -> satisfiable by scandipwa/scandipwa[0.0.1].
    - scandipwa/scandipwa 0.0.1 requires scandipwa/catalog-graphql 3.1.25 -> found scandipwa/catalog-graphql[dev-master] but it does not match the constraint.
```

In order to solve it, you have to head to the root `composer.json` and update the version manually. Then do the update command from the above.

### Version Configuration

Please note that you don't have to edit the package `composer.json` and specify it's version. The `dev-master` is already the latest version you could have.

If you experiencing some issues with it, you might want to configure the root `composer.json` like this:

```
"minimum-stability": "dev",
"prefer-stable": false
```

When you switch the branches in the package while development, there shouldn't be any issue with the version you have installed, since the package directory is symlinked and will sync all changes to the `vendor` directory. But if you encounter any issue because you have a different branch, just change `dev-master` to `dev-branch-name` in the root `composer.json`.

Same issue will happen when you have a different default branch, like `main` instead of `master`.

In order to check whether the ScandiPWA package is symlinked, you can launch the following command:

```
ls -lF vendor/scandipwa
```

It will display all symlinks and their targets.


# Docker Setup \[deprecated]

Before toolchains like CMA and CSA appeared, ScandiPWA only supported one build mode – Magento 2 theme. To develop it, we had a custom docker-setup configured.

{% hint style="danger" %}

### Heads up!

This approach is no longer supported. It is considered to be legacy, deprecated way to setup ScandiPWA.
{% endhint %}


# Migrating to CMA & CSA

If you have already set up ScandiPWA with docker, you can upgrade to the new setup

Previously, ScandiPWA used a [docker setup](https://github.com/scandipwa/magento-docker). This configuration is now considered legacy, and you can easily upgrade to the new `create-scandipwa-app` setup.

## Before You Start

Before migrating, make sure that you have backed up the current state of the project, preferrably with a version control system such as Git. Verify that your system meets the [requirements](/getting-started-1#prerequisites).

## Creating the New Setup

In the new setup, the theme is developed in the `localmodules` directory. If it does not exist yet, create it in your project's source directory:

```bash
mkdir src/localmodules
cd src/localmodules
```

{% tabs %}
{% tab title="If you want to use the ScandiPWA theme" %}
Now, using the new `create-scandipwa-app` script, you can re-create the theme (replace `<your-app-name>`):

Using Yarn (recommended):

```
yarn create scandipwa-app <your-app-name>
```

Using `npx` (if you don't have yarn):

```bash
npx create-scandipwa-app <your-app-name>
```

It will take a few minutes for the script to download the required packages and initialize the theme. When it completes, navigate to the newly-created theme:

```bash
cd <your-app-name>
```

{% endtab %}

{% tab title="If you are a ScandiPWA contributor" %}
Clone the ScandiPWA repo in localmodules:

```bash
git clone https://github.com/scandipwa/scandipwa
cd scandipwa
```

Install dependencies:

```
yarn
```

In the instructions below, `<your-app-name>` will be "scandipwa".
{% endtab %}
{% endtabs %}

Run the compilation process in magento mode:

{% tabs %}
{% tab title="yarn (recommended)" %}

```bash
BUILD_MODE=magento yarn start
```

{% endtab %}

{% tab title="npm" %}

```
cd <your-app-name>
BUILD_MODE=magento npm start
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
You will need to keep this process running – it continuously re-builds the theme when changes are made. Open a new terminal tab to enter new commands.
{% endhint %}

## Switching to the New Setup

Now the new theme is created, but we need to reconfigure the project to use it instead of the old theme. The changes we need are easiest to make from within the docker container, so start the app and gain bash access to it:

{% code title="From the project root" %}

```bash
dc up -d
inapp bash # to enter the app container
```

{% endcode %}

#### Removing the Old Setup

Previously, our theme was installed via the `scandipwa/installer` package, but we no longer require it. To clean up and avoid confusion, remove it:

{% code title="(from within the app container)" %}

```bash
composer remove scandipwa/installer
```

{% endcode %}

#### Adding the New Setup

We will install the newly-created theme by taking advantage of Composer's ability to install from [local repository sources](https://getcomposer.org/doc/05-repositories.md#path). First, we add our theme as a local repository source. This will alter `composer.json` to add a new item in the `repositories` field:

{% tabs %}
{% tab title="If you want to use the ScandiPWA theme" %}
{% code title="(from within the app container)" %}

```bash
composer config repo.theme path localmodules/<your-app-name>
```

{% endcode %}
{% endtab %}

{% tab title="If you are a ScandiPWA contributor" %}
{% code title="(from within the app container)" %}

```bash
composer config repo.theme path localmodules/scandipwa/packages/scandipwa
```

{% endcode %}
{% endtab %}
{% endtabs %}

Next, we install our theme by using `require`. This will resolve the package to the localmodules directory we configured above:

{% tabs %}
{% tab title="If you want to use the ScandiPWA theme" %}
{% code title="(from within the app container)" %}

```bash
composer require scandipwa/<your-app-name>
```

{% endcode %}
{% endtab %}

{% tab title="If you are a ScandiPWA contributor" %}
{% code title="(from within the app container)" %}

```
composer require scandipwa/scandipwa
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### Making the Switch

Run the `upgrade` command and disable full-page caching:

{% code title="(from within the app container)" %}

```bash
bin/magento setup:upgrade
bin/magento cache:disable full_page
```

{% endcode %}

It is now time to enable the new theme. In the Magento admin panel, navigate to **Content** > **Design** > **Configuration**. Edit the scope you want to change (typically the most general one in the list), and select the new theme. Finally, flush the cache:

{% code title="(from within the app container)" %}

```bash
bin/magento cache:flush
```

{% endcode %}

{% hint style="success" %}
The new theme should now be served on the frontend! For now, it will be the default ScandiPWA theme, because we haven't migrated the cusomized code yet.
{% endhint %}

{% hint style="warning" %}

### Heads up!

The "[hot-reload](https://webpack.js.org/concepts/hot-module-replacement/)" feature only works in **insecure HTTP mode** of your Magento 2 server.
{% endhint %}

## Migrating the Code

You can now copy the code from your old theme into the new setup. However, note that the file structure has changed to improve organization, and you will need to make adjustments:

| Old Path                                                                                                                                                        | Changes                                                                                                                                                                                                                                                 |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <p><code>etc</code></p><p><code>registration.php</code></p><p><code>theme.xml</code></p><p><br><em>\<any other Magento-specific file you have created></em></p> | Magento files now moved under a new `magento` directory for better organization. The new paths are `magento/etc`, `magento/registration.php`, etc.                                                                                                      |
| `src/public`                                                                                                                                                    | <p>Now moved outside <code>src</code>, the new path is <code>public</code>.</p><p>The file <code>index.production.phtml</code> is renamed to <code>index.php</code>, and <code>index.development.html</code> is renamed to <code>index.html</code>.</p> |
| `src/config`                                                                                                                                                    | <p>Removed. The webpack configuration now lives in the </p><p><code>@scandipwa/scandipwa-scripts</code> package. To modify this configuration, use build configuration plugins.</p>                                                                     |
| `src/app`                                                                                                                                                       | Now the contents of `app` are moved to `src`, and `app` no longer exists. The new path is `src`. This was done to create a more shallow and easy to navigate file structure.                                                                            |

Once your code has been copied, you can delete the old location of your theme. Your app should now work as expected with the new setup!

## Extension mechanism changes

The extensions are no longer Composer packages, but NPM packages. Thus, the frontend and backend (Magento 2) extension are now separated. Internal file structure is also different. Previously, the extensions had to be created under then `scandipwa/app` folder of the composer package, now, you can simply put your code into extension's `src` folder. Inside of this folder, the file-structure is unchanged.

You can read more about new extension registration sytax [here](https://scandipwa.gitbook.io/create-scandipwa-app/extensions/extensions), it replaces the legacy `scandipwa.json` file.

{% hint style="info" %}
A tool for automated extension transformation from the old to the new format is available for use [on GitHub](https://github.com/scandipwa/split-legacy-extension).&#x20;
{% endhint %}


# Override Mechanism

Create a customized theme based on ScandiPWA

Overriding files in the theme is a great solution for store owners and developers that want to have a theme based on ScandiPWA, but need to customize it for their needs. Using the override mechanism, you are able to override any part of the ScandiPWA theme, including styling, user interaction, page structure, or custom functionality. Any part that you don't need to override will fallback to the default ScandiPWA implementation.

Watch the video tutorial/showcase by following the link below!

{% content-ref url="/pages/-MQM0kNuHQO5t2DbwgM3" %}
[#3 Overriding a file](/tutorials/video-tutorials/overriding-a-file)
{% endcontent-ref %}

The Override Mechanism enables you to override any JavaScript or SCSS stylesheet file while keeping what you want to keep from the original file. You can of course create new files and use them in the files you override. Using this mechanism, you will be able to:

* Change the layout or styling of any component or page
* Modify any component's behavior
* Add new components with custom functionality
* Add new pages to your application
* Remove any unwanted functionality
* ...and more!&#x20;

{% hint style="warning" %}
The Override Mechanism is designed to be used for developing one specific theme. If you want to develop an extension that can be installed on multiple themes, take a look at the [Plugin Mechanism](/developing-with-scandi/extensions), which can be used to develop reusable plugins.
{% endhint %}

## Override Mechanism Guides

{% content-ref url="/pages/-MNcP\_Mzb\_fzWwn4SQ1y" %}
[Overriding JavaScript](/developing-with-scandi/override-mechanism/extending-javascript)
{% endcontent-ref %}

{% content-ref url="/pages/-MNcRT69Hjndi55WsIsz" %}
[Overriding Styles](/developing-with-scandi/override-mechanism/extending-styles)
{% endcontent-ref %}

{% content-ref url="/pages/-MOjieryc8iY8bkRdoj8" %}
[Overriding the HTML / PHP](/developing-with-scandi/override-mechanism/overriding-the-index-file)
{% endcontent-ref %}

## Parent themes

You can also stack overrides on top of each other, by creating parent themes. Learn more in the guide below:

{% content-ref url="/pages/-MPDiXo7GhIaBrGeZ3ej" %}
[Parent Themes](/developing-with-scandi/override-mechanism/parent-themes)
{% endcontent-ref %}


# Overriding JavaScript

Override JavaScript files using the ScandiPWA Override Mechanism

When developing a ScandiPWA-based theme, you have the ability to override any JavaScript file and its exports. To override a JavaScript file, create a new file in your theme `src` directory matching the path of the file you want to override. For example, if you want to override the theme file in `component/ProductCard/ProductCard.component.js`, create a file with the same path in your theme's source directory, `src`:

{% code title="override: component/ProductCard/ProductCard.component.js" %}

```jsx
import { PureComponent } from 'react';

export class ProductCard extends PureComponent {
    render() {
        return (
            <p>
                We have overridden the ProductCard component!
            </p>
        );
    }
}

export default ProductCard;
```

{% endcode %}

Now, any import using the alias `Component/ProductCard/Component/ProductCard.component` will resolve to your newly-created file. Indeed, if you check the product list page, you will see the updated component:

![](/files/-MNcQWobHmjVNubImvU3)

## Detailed overriding examples

Despite the general rule being simple, it is important to learn more details. See the detailed guide on overriding class-based and non-class-based files below.

{% content-ref url="/pages/-MV1eywHRTP7i-3joJLh" %}
[Overriding classes](/developing-with-scandi/override-mechanism/extending-javascript/overriding-classes)
{% endcontent-ref %}

{% content-ref url="/pages/-MV1f2OzsGu5HXo42hWZ" %}
[Overriding non-classes](/developing-with-scandi/override-mechanism/extending-javascript/overriding-non-classes)
{% endcontent-ref %}

## Overriding Tutorial

Specific examples of how to use the Override Mechanism for common tasks can be found in our [tutorial](/tutorials/customizing-your-theme/customizing-javascript)!

{% content-ref url="/pages/-MNcSxs181lV3ZBK6-HK" %}
[Broken mention](broken://pages/-MNcSxs181lV3ZBK6-HK)
{% endcontent-ref %}


# Overriding classes

Virtually always, you will want to keep part of the default functionality and only modify some aspect of the existing class. This is how the theme is usually extended.

For example, when overriding `ProductCard`, suppose we want to keep the default ScandiPWA implementation of the component, but with one change - we don't want the product images to be visible in the product cards.

In order to do this, we will make sure our class extends the base `ProductCard` class. Then, it will inherit the default functionality from its parent class, and we will be able to override any part of it.

All we need to do is import the original `ProductCard` class, extend it, and re-export the modified version. But there is one challenge: as mentioned before, if we try to import from `Component/ProductCard/ProductCard.component`, the import mechanism will resolve this to our overridden implementation, not the desired original class. Hence, it is impossible to import the original class from the parent theme using the `Component` alias if we intend to override this class.

The solution is to use another alias, `SourceComponent` to extend classes from the parent theme.

{% hint style="info" %}
The ScandiPWA import mechanism provides the `Source` aliases to enable you to directly import from the parent theme, regardless of whether there is an overridden version availabl&#x65;**.** Similarly, you can use the `SourceRoute` alias to import directly from a route defined in the parent theme, or the `SourceUtil` alias to import an utility file from the parent theme.&#x20;
{% endhint %}

{% code title="override: component/ProductCard/ProductCard.component.js" %}

```jsx
import { PureComponent } from 'react';

// we use the SourceComponent alias to explicitly import from the parent theme
// (if we would use Component/ProductCard/... instead, we would be trying to import
// the current file, which would result in an error)
import { ProductCard as SourceProductCard } from 'SourceComponent/ProductCard/ProductCard.component';

// we imported the original ProductCard class defined in the parent theme.
// we aliased the import to `SourceProductCard` to indicate that SourceProductCard
// is the parent theme version of the class

// you should always copy over the namespace declaration when overriding an existing class
// to avoid breaking plugins
/** @namespace Component/ProductCard/Component */
export class ProductCard extends SourceProductCard { // we can now extend SourceProductCard,
    // and override any methods we want to change

    // this method overrides the default implementation provided in the original ProductCard class
    renderPicture()  {
        // returning null in a React component means rendering nothing
        return null;
    }
}

// we now export the extended and modified version of the class
export default ProductCard;
```

{% endcode %}

{% hint style="success" %}
You should never have to copy any code from the parent theme to reuse it. **It is a good practice to always import functions, classes, and values from the parent theme when you want to extend them**, as shown above. Following this pattern will result in cleaner code - it will be immediately clear which functions your code changes by overriding them. In addition, there will be less code to maintain, resulting in faster development.
{% endhint %}

Indeed, we can check that now, the `ProductCard` component is the same as in the default theme, with one change: it no longer has a picture.

![](/files/-MNcQb3h9A7ntK_uqp_6)

{% hint style="warning" %}
When overriding a JavaScript file, **you should make the same exports as the file you are overriding**. If the original theme file exports a certain constant, it is possible that other classes (or third party plug-ins) will expect that they can import this value. If you forget to re-export it when overriding the file, these imports will break. You are of course allowed to add new exports and modify existing ones, but please be careful to never leave out an export.
{% endhint %}

### Overriding Constructors

ScandiPWA uses magic `__construct` functions instead of constructors. This is done to enable overriding of these functions, which you can override like any other function.


# Overriding non-classes

Virtually always, you will want to keep part of the default functionality and only modify some aspect of the existing file. This is how the theme is usually extended.

This is easy-to-achieve with class, where you can extend the original file providing some custom functionality. But what to do if an override of the constant or function is intended?

Following are the rules, which will help you to stay "compatible" (thus ensuring easier upgrades):

## 1. Do not copy files

Instead of copy-pasting a file, prefer manually exporting original functions and constants, and then re-exporting ones you changed!

{% hint style="info" %}
The ScandiPWA import mechanism provides the `Source` aliases to enable you to directly import from the parent theme, regardless of whether there is an overridden version availabl&#x65;**.** Similarly, you can use the `SourceRoute` alias to import directly from a route defined in the parent theme, or the `SourceUtil` alias to import a utility file from the parent theme.&#x20;
{% endhint %}

Let's now consider an example. Here, we would like to override a `MIN_PASSWORD_LENGTH` defined in the `component/Form/Form.config.js` file. Here is how we can do it (**without copying the file**):

{% code title="override: component/Form/Form.config.js" %}

```javascript
// exporting all functions and constants from original file
export * from 'SourceComponent/Form/Form.config.js';
// specifically exporting default (as it is not included in "*")
export { default } from 'SourceComponent/Form/Form.config.js';
// re-exporting the overriden variable
export const MIN_PASSWORD_LENGTH = 6;
```

{% endcode %}

## 2.  Call original functions if possible

Instead of copying the original function from the source file, consider importing and calling it, then processing the original output value.&#x20;

Let's now consider an example. Here, we would like to override a `mapDispatchToProps` defined in the `component/AddToCart/AddToCart.container.js` file. Here is how we can do it (**without copying the original function**):

{% hint style="warning" %}

### Heads up!

When overriding a function that is used later in the component (for example in default export), you must also override the place the function is being used (therefore, as per example, default export).
{% endhint %}

{% code title="overriding: component/AddToCart/AddToCart.container.js" %}

```javascript
// imporing original function to call
import {
    AddToCartContainer,
    mapStateToProps,
    mapDispatchToProps as sourceMapDispatchToProps
} from 'SourceComponent/AddToCart/AddToCart.container.js';

// importing to replicate original default export
import { connect } from 'react-redux';

// exporting all functions and constants from original file
export * from 'SourceComponent/AddToCart/AddToCart.container.js';

// re-exporting the overriden variable
export const mapDispatchToProps = (dispatch) => {
    // calling the original function to modify the result
    const handler = sourceMapDispatchToProps(dispatch);

    // add custom functionality    
    handler.logProduct = () => {
        console.log('product added to cart');
    };
    
    // returning modified result
    return handler;
}

// exporting default
// (becuase function mapDispatchToProps was used in it)
export default connect(mapStateToProps, mapDispatchToProps)(AddToCartContainer);
```

{% endcode %}


# Overriding Styles

You can adjust the styling of your theme in 3 different ways:

* [**Overriding the Base Styles**](/developing-with-scandi/override-mechanism/extending-styles#overriding-the-base-styles): The ScandiPWA theme includes a set of stylesheets in `app/style` that define the main look of the application. These define global settings, such as SCSS variables (`style/abstract/_variables.scss`), media query definitions (`style/abstract/_media.scss`), base styling for some HTML elements (`style/base`), CMS content styling (`style/cms`), and more. If you want to make global styling changes to these files, you can override them.
* [**Replacing a Component's Styles**](/developing-with-scandi/override-mechanism/extending-styles#replacing-a-components-styles): Every component (and route) includes an associated stylesheet, named `<component>.style.scss`. If you wish to completely change the look of a component, you can replace these styles with your own.
* [**Partially Overriding a Component's Styles**](/developing-with-scandi/override-mechanism/extending-styles#partially-overriding-a-components-styles): You can choose to keep the existing component's styles instead, and override some of them.

## Overriding the Base Styles

The base styles are defined in the `styles` directory, and imported in `styles/main.scss`. In order to override a base style file, you need to copy over the chain of files that is used to import it.

For example, suppose we want to change the main theme color to an ocean blue. The primary color is defined in `style/abstract/_variables.scss`. Copy over this file to avoid missing any variables, and make the desired changes:

{% code title="override: style/abstract/\_variables.scss" %}

```css
$white: #fff;
$black: #0a0a0a;
$default-primary-base-color: #2387f2; // changed the color here
$default-primary-dark-color: #1259d4; // and here
$default-primary-light-color: #42bdf7; // and here!
$default-secondary-base-color: #eee;
$default-secondary-dark-color: #929292;
$default-secondary-light-color: #f8f8f8;
$font-muli: 'Muli', sans-serif;
$font-standard-size: 12px;
$font-mobile-size: 14px;
```

{% endcode %}

We also need to copy over the file that imports `_variables.scss` for the override to work. By inspecting the styling files, we can see that `style/abstract/_variables.scss` is imported in `style/abstract/_abstract.scss`, which is itself imported directly from `style/main.scss`. Hence, we need to copy over `_abstract.scss` and `main.scss` to their respective directories in our theme.

We can check that the theme's primary color has been successfully overridden:

![](/files/-MNcRj_gBjkcKtmfaDnO)

## Replacing a Component's Styles

Sometimes you may want to completely restyle a component, ignoring all original styles. For example, suppose we want to create an entirely new design for the `CategoryPaginationLink` component. All we need to do is create a stylesheet with the same path as the stylesheet from the parent theme we want to override. In this case, create a file in `component/CategoryPaginationLink/CategoryPaginationLink.style.scss` with the following contents:

{% code title="override: component/CategoryPaginationLink/CategoryPaginationLink.style.scss" %}

```css
:root{
    --category-pagination-link-size: 50px;
    --category-pagination-link-text-size: 30px;
}

.CategoryPaginationLink {
    // configure layout and center text
    display: block;
    text-align: center;
    size: var(--category-pagination-link-text-size);
    line-height: var(--category-pagination-link-size);

    // set a circular shape with border-radius
    width: var(--category-pagination-link-size);
    height: var(--category-pagination-link-size);
    margin: 6px;
    border-radius: var(--category-pagination-link-size);

    // white text on a blue background
    background: var(--primary-base-color);
    color: #fff;

    // indicate hover/active state
    transition: opacity 100ms ease-out;
    opacity: 0.7;

    &:hover,
    &:focus,
    &_isCurrent {
        opacity: 1;
    }
}
```

{% endcode %}

With this override, any import of the `CategoryPaginationLink` stylesheet will resolve to our custom file. Hence, our stylesheet will be the one to be included in the final CSS file, resulting in this look:

![](/files/-MNcRokCUsrgMCyUALqH)

## Partially Overriding a Component's Styles

In some situations, you might want to keep the overall look of a component but change some minor aspect of it's styling. In such cases, it would be undesirable to copy the original stylesheet just to change a few lines of SCSS rules. ScandiPWA suggests a better approach: keeping the original stylesheet and creating an additional stylesheet that overrides some of the default styles.

Consider an example where we want to change the style of the heading of `MyAccountOverlay` - we want to make the text "Sign in to your account" italic.

First, we can define the styles we want to change. We can't keep the original name of the file, because then we will override it completely as shown in the previous section. The naming convention for partially overriding stylesheet files in ScandiPWA is to add `.override` to the file name:

{% code title="override: component/MyAccountOverlay/MyAccountOverlay.override.style.scss" %}

```css
.MyAccountOverlay {
    &-Heading {
        font-style: italic;
    }
}
```

{% endcode %}

The styles we want to override are defined, but they will not be included in the CSS requested by the browser unless we import them somewhere. Styles in ScandiPWA are typically imported from the `.component` file, so override the `.component` file of `MyAccountOverlay`, by creating a file matching the original component file. We don't need to change any of the exported classes or components, so we can leave the existing exports unchanged, but we do need to add an additional import - our overridden styles:

{% code title="override: component/MyAccountOverlay/MyAccountOverlay.component.js" %}

```jsx
// import the original component so that base styles are applied first
import 'SourceComponent/MyAccountOverlay/MyAccountOverlay.component';
// import our modified styles to override some of the default ones
import './MyAccountOverlay.override.style.scss';

// re-export all the original exports from the parent theme untouched
export * from 'SourceComponent/MyAccountOverlay/MyAccountOverlay.component';
export { default } from 'SourceComponent/MyAccountOverlay/MyAccountOverlay.component';
```

{% endcode %}

Now, we can verify that the new CSS rules have overridden some of the default ones, and the title is in italics:

![](/files/-MNcRuaPJiD638Pe9CYI)


# Overriding the HTML / PHP

You can override index.html and any other static file by matching its path

By default, ScandiPWA provides this index file:

{% code title="source: public/index.html" %}

```markup
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <!-- Muli font import from Adobe -->
    <link rel="stylesheet" href="https://use.typekit.net/gbk7rfi.css">

    <!-- Default Meta -->
    <title>ScandiPWA</title>
    <meta name="theme-color" content="#ffffff" />
    <meta name="description" content="Web site created using create-scandipwa-app" />

    <!-- Default content-configurations -->
    <script>
        window.contentConfiguration = {};
        window.storeList = ['default'];
        window.storeRegexText = `/(${window.storeList.join('|')})?`;
    </script>
</head>
<body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
</body>
</html>
```

{% endcode %}

If you want to change any of these values, or import additional assets, you will need to override this file. Simply copy it to `public/index.html` of your theme's directory, and make the changes you need.

When compiling into Magento 2 theme, the application is using different file, the `public/index.php`:

{% code title="source: public/index.php" %}

```php
<?php
    $colorConfig = $this->getThemeConfiguration('color_customization');
    $contentConfig = $this->getThemeConfiguration('content_customization');
    $icons = $this->getAppIconData();
?>
<!DOCTYPE html>
<html lang="<?= $this->getLocaleCode() ?>">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover">

        <!-- Muli font import from Abode -->
        <link rel="stylesheet" href="https://use.typekit.net/gbk7rfi.css">

        <script>
            (function() {
                if (typeof globalThis === 'object') return;
                Object.prototype.__defineGetter__('__magic__', function() {
                    return this;
                });
                __magic__.globalThis = __magic__;
                delete Object.prototype.__magic__;
            }());

            window.actionName = { type: `<?= $this->getAction(); ?>` };
            window.contentConfiguration = <?= json_encode($contentConfig) ?> || {};
            window.storeList = JSON.parse(`<?= $this->getStoreListJson() ?>`);
            window.storeRegexText = `/(${window.storeList.join('|')})?`;
        </script>

        <!-- Icons -->
        <link rel="shortcut icon" href="/pub/media/favicon/favicon.png">

        <?php foreach ($icons['ios_startup'] as $icon): ?>
            <?= sprintf('<link rel="apple-touch-startup-image" sizes="%s" href="%s">', $icon["sizes"], $icon["href"]); ?>
        <?php endforeach; ?>

        <?php foreach ($icons['ios'] as $icon): ?>
            <?= sprintf('<link rel="apple-touch-icon" sizes="%s" href="%s">', $icon["sizes"], $icon["href"]); ?>
        <?php endforeach; ?>

        <?php foreach ($icons['icon'] as $icon): ?>
            <?= sprintf('<link rel="icon" sizes="%s" href="%s">', $icon["sizes"], $icon["href"]); ?>
        <?php endforeach; ?>

        <!-- Manifest -->
        <link rel="manifest" href="/pub/media/webmanifest/manifest.json">
    <style>
        <?php if ($colorConfig['enable_color_customization']['enable_custom_colors'] !== "0"): ?>
            <?php $colorArray = $colorConfig['primary_colors'] + $colorConfig['secondary_colors']; ?>
            :root {
                <?php foreach ($colorArray as $code => $color): ?>
                    <?php if (strpos($code, 'color') !== false): ?>
                        <?= sprintf('--imported_%s: #%s;', $code, $color); ?>
                    <?php endif; ?>
                <?php endforeach; ?>
            }
        <?php endif; ?>
    </style>
</head>
<body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
</body>
</html>
```

{% endcode %}

If you want to change any of these values, or import additional assets, you will need to override this file. Simply copy it to `public/index.php` of your theme's directory, and make the changes you need.


# Parent Themes

Let others base their theme on yours

The [Override Mechanism](/developing-with-scandi/override-mechanism) is about taking a base parent theme, and overriding parts of it to create a new, customized theme. By default, that parent theme is `@scandipwa/scandipwa`, the standard ScandiPWA theme – but it doesn't have to be!

Any theme created using `create-scandipwa-app` can be extended. Read the [CSA documentation](https://scandipwa.gitbook.io/create-scandipwa-app/themes/extensions-and-themes) to learn more.


# Extensions

ScandiPWA allows you to create "plug-and-play" extensions for your applications.

{% hint style="info" %}
**Extension** - is a reusable, isolated part of your application. It can contain a presentation layer, business logic, or even build configuration.
{% endhint %}

Extensions commonly come as two packages:

* [Magento 2 module](/developing-with-scandi/extensions#magento-2-module) – back-end only (GraphQl, Admin UI, etc.)
* [ScandiPWA extension](/developing-with-scandi/extensions#scandipwa-extension) – front-end only (presentation)

### Looking for an extension?

Visit [ScandiPWA marketplace](https://marketplace.scandipwa.com/modules.html) to find a list of ones already available for download!

## Magento 2 module

Usually, a composer package (or ZIP archive), which contains back-end functionality extensions of Magento 2. The common functionality they provide (in ScandiPWA context) are GraphQl endpoints and Magento 2 admin modifications. In case you plan to create or install one, refer to the guide below.

{% content-ref url="/pages/-MVF\_BE8VZhydDrNynuE" %}
[Working with Magento modules](/developing-with-scandi/working-with-magento/working-with-magento-modules)
{% endcontent-ref %}

## ScandiPWA extension

An NPM package, containing a presentation layer (front-end) for your feature. In case you plan to create, install, or publish one, refer to the guides below.

{% content-ref url="/pages/-MVFgFT8\_4bZj6ohK\_qC" %}
[Creating an extension](/developing-with-scandi/extensions/creating-an-extension)
{% endcontent-ref %}

{% content-ref url="/pages/-MW-h6S55TA9fiSZkt5e" %}
[Migrating from 3.x to 4.x](/developing-with-scandi/extensions/migrating-from-3.x-to-4.x)
{% endcontent-ref %}

{% content-ref url="/pages/-MVFgmDmU-Xa-\_WiW8wn" %}
[Installing an extension](/developing-with-scandi/extensions/installing-an-extension)
{% endcontent-ref %}

{% content-ref url="/pages/-MVFo42DGwlckfpYgsJj" %}
[Broken mention](broken://pages/-MVFo42DGwlckfpYgsJj)
{% endcontent-ref %}


# Creating an extension

For NPM package to be considered an extension, it must have a `package.json` field `scandipwa.type` equal to `extension`. For example:

```javascript
{
    "scandipwa": {
        "type": "extension"
    }
    ...
}
```

The **extensions are power-less without a theme**. The application compiles from theme sources. Extensions are just isolated pieces, which make some features of your application work.

Any package containing the `@namespace` magic comments **must** have a `scandipwa` block declared in its package.json file. Otherwise, the `@namespace` magic comments will not be handled correctly during the build time, hence will not work.

## Registering an extension <a href="#registering-an-extension" id="registering-an-extension"></a>

**Both themes and extensions can register extensions**. In order to do it, the extension should be added to `scandipwa.extensions` object of `package.json` . The extension must be a valid NPM package, therefore you are required to add it into `dependencies` field of your `package.json`. Like so:

```javascript
{
    "dependencies": {
        "@scandipwa/m2-theme": "0.0.2",
        ...
    },
    "scandipwa": {
        "extensions": {
            "@scandipwa/m2-theme": true
        },
        ...
    }
    ...
}
```

This process could be automated using [ScandiPWA CLI](https://docs.create-scandipwa-app.com/building-your-app/using-scandipwa-cli). First, make sure you are in your theme's root folder. Now, to install an extension from the NPM registry, you can use the following command:

```bash
scandipwa extension install <PACKAGE>
```

In order to create a new extension, use the following command:

```bash
scandipwa extension create <PACKAGE>
```

This command will create a new extension in the `packages` folder and register it in your current theme. It will be symlinked from `packages` to proper folder under `node_modules`.

## Enabling an extension <a href="#enabling-an-extension" id="enabling-an-extension"></a>

Enabling and disabling an extension is achieved by setting the `true`or `false` as a value of the extension key in `scandipwa.extensions` . Your theme can control enabled extensions across the whole application. This allows disabling the previously enabled extension. The sequence of preference in this case is:

1. Your theme enabled extensions
2. Your theme's parent themes enabled extensions
3. Extension enabled extensions

{% hint style="warning" %}

### Watch out! <a href="#watch-out" id="watch-out"></a>

Disabling a previously enabled extension can lead to layout-shifts and issues inside of your parent theme. Also, there might be extensions plugging into the extension you intend to disable, this can lead to unexpected results.
{% endhint %}

## Extension features <a href="#extension-features" id="extension-features"></a>

Extensions are a very important part of the ScandiPWA tool-chain. The following features are available:

* ​[Application plugins](https://docs.create-scandipwa-app.com/extensions/application-plugins)​
* ​[Build configuration plugins](https://docs.create-scandipwa-app.com/extensions/build-configuration-plugins)​
* ​[Module preference](https://docs.create-scandipwa-app.com/extensions/virtual-modules)​
* ​[File provision](https://docs.create-scandipwa-app.com/extensions/file-provision)
* [Translation bundles](https://docs.create-scandipwa-app.com/building-your-app/internationalization#translation-sources)


# Installing an extension

ScandiPWA setup considers having the back-end (Magento 2 Module) and front-end (ScandiPWA Extensions) of your application completely separated. If you are looking for back-end part instructions, refer to the link below.

{% content-ref url="/pages/-MVF\_BE8VZhydDrNynuE" %}
[Working with Magento modules](/developing-with-scandi/working-with-magento/working-with-magento-modules)
{% endcontent-ref %}

## Install from a local source

{% hint style="info" %}
**Note**: The following instructions are valid for **ScandiPWA 4.x** For **5.x**, please go to <https://marketplace.scandipwa.com/extension-manual-installation-guide>
{% endhint %}

1. Download the archive containing the FE part of the extension from the marketplace.
2. Create a `packages/` directory inside of your theme's root.
3. Put the archive's contents inside of the `packages/<package name>` directory. Make sure that you have a `packages/<package name>/package.json` file present alongside all the other extension's contents, that means that you have unpacked the extension correctly. Note: `<package name>` can also include publisher, `@scandipwa/paypal` is a valid package name.
4. Add the following scripts to the `scripts` section of your theme's `package.json` file. This is necessary for your package to be symlinked into the `node_modules` directory of your theme after manipulations with dependencies

   ```javascript
   {
       "scripts": {
           "postinstall": "scandipwa-scripts link",
           "postupdate": "scandipwa-scripts link"
       }
   }
   ```
5. Add the extension to the dependencies of your theme, as follows:

   ```javascript
   {
       "dependencies": {
           "<package name>": "file:packages/<package name>"
       }
   }
   ```
6. Update the symlinks by running the following command

   ```bash
   # For yarn users
   yarn postinstall

   # For npm users
   npm run postinstall
   ```
7. [Enable the extension](/developing-with-scandi/extensions/installing-an-extension#enable-the-extension)

## Install with a package manager (beta)

Some of the FE ScandiPWA extensions are available for installation using `npm` and `yarn`.

The process is different from the regular module's installation, the ScandiPWA packages are stored in our own registry - `r.scandipwa.com`

The installation process is the following:

1. Get the [credentials](https://marketplace.scandipwa.com/scandipwa_npmregistry/index/auth/) (token) from the [marketplace](https://marketplace.scandipwa.com/)
2. Configure the token for your project

   2.1. Create an `.npmrc` file inside of your project's root directory, neighboring to the `package.json` file. The `.npmrc` file should be created even if you are using `yarn`.

   2.2. Put your token in there in the following format:

   ```bash
   TOKEN=put.token.here
   //r.scandipwa.com/:_authToken=$TOKEN
   ```
3. Add the desired package to your project's dependencies (and fetch it)

   ```bash
   # Write a full command
   yarn --registry https://r.scandipwa.com add <package>
   npm --registry https://r.scandipwa.com i <package>

   # Or create an alias
   alias yr='yarn --registry https://r.scandipwa.com'
   alias npr='npm --registry https://r.scandipwa.com'

   # and use it
   yr add <package>
   npr i <package>
   ```
4. When installing your project's dependencies, don't worry about any additional actions. The `yarn.lock` or `package-lock.json` will contain all the necessary data

   ```bash
   # For yarn users
   yarn

   # For npm users
   npm ci
   ```
5. [Enable the extension](/developing-with-scandi/extensions/installing-an-extension#enable-the-extension)

## Enable the extension

Turn on the extension in your theme's `package.json` file. You may turn it off by changing `true` to `false` in the corresponding `extensions` block's entry.

```javascript
    {
        "scandipwa": {
            "extensions": {
                "<package name>": true
            }
        }
    }
```

## Install by using `scandipwa-cli`

Currently, it is possible to install the extensions published to `npm` with the `scandipwa-cli` package, by using `scandipwa extension install` command.

It is planned to provide support for installing local extensions in the future.

More information about installing via `scandipwa-cli` from `npm` registry see [here](https://docs.scandipwa.com/developing-with-scandi/developer-tools/scandipwa-cli).


# Migrating from 3.x to 4.x

Previously (before version 4.0.0) ScandiPWA used a single package for both Magento 2 module and ScandiPWA extension.

{% hint style="info" %}

### Not sure which extension version you have?

The following conditions indicate the belonging to an old version:

* The presence of `scandipwa/app` folder
* The presence of `scandipwa.json` file
  {% endhint %}

To use this version with ScandiPWA version 4.0.0 and above, please use following instructions:

{% tabs %}
{% tab title="Instructions" %}

1. Clone the <https://github.com/scandipwa/split-legacy-extension> repository using the following command: `git clone git@github.com:scandipwa/split-legacy-extension.git`
2. Run the following command:`node <path to cloned projject>/index.js <source path> [<destination path>]`
3. Notice that in your `<destination path>` directory two new directories appeared: frontend and backend. These directories are npm and composer modules, correspondingly.
4. Validate the new modules. Things to verify:
   1. All first-level children of the initial directory have found their path to the correct modules. E.g. if you have some `.editorconfig` in the root of your initial module, it will not be copied - the variety of files there can be endless and it is ambiguous where should they go. You are expected to handle that yourself. This tool only handles the files vital for the ScandiPWA plugin system.
   2. Both `composer.json` and `package.json` files exist and are valid, with relevant information.
5. Enjoy the FE-only extension in your `create-scandipwa-app` setup and the BE-only module on your M2 instance!

{% content-ref url="/pages/-MVFgmDmU-Xa-\_WiW8wn" %}
[Installing an extension](/developing-with-scandi/extensions/installing-an-extension)
{% endcontent-ref %}

{% content-ref url="/pages/-MVF\_BE8VZhydDrNynuE" %}
[Working with Magento modules](/developing-with-scandi/working-with-magento/working-with-magento-modules)
{% endcontent-ref %}
{% endtab %}

{% tab title="Example" %}

1. Let's clone the tool into `~` – home directory: `git clone git@github.com:scandipwa/split-legacy-extension.git ~/split-legacy-extension`
2. Our extension is located in `~/Downloads/my-extension-v3` let's transform it, and save it into `~/Downloads/my-extension-v4`:`node ~/split-legacy-extension/index.js ~/Downloads/my-extension ~/Downloads/my-extension-v4`
3. Now if we check the `~/Downloads/my-extension-v4` we should see the `frontend` and `backend` folders there!
   {% endtab %}
   {% endtabs %}


# Extension Terminology

A document defining and standardizing plugin-related terminology for consistency and clarity across documentation, tutorials and inter-developer communication.

## Modules

[**Magento Module**](https://devdocs.magento.com/guides/v2.4/architecture/archi_perspectives/components/modules/mod_intro.html) – a logical group – that is, a directory containing blocks, controllers, helpers, models – that are related to a specific business feature

**GraphQL Module** – a Magento Module that provides a [GraphQL](/structure/building-blocks-summary/constructing-graphql-queries) interface, or improves an existing one. By convention, its name ends with the `GraphQl` suffix (examples: `GtmGraphQl`, `PayPalGraphQl`)

[**Scandi Extension**](/developing-with-scandi/extensions) – a separate, reusable JavaScript package implementing some frontend features in Scandi. The module’s name should be in `lower_snake_case`, and may optionally be prefixed with a vendor such as `@scandipwa/` (example: `@scandipwa/paypal_payments`). Often located under `scandipwa/packages` folder or in `node_modules`.&#x20;

## Plugins

**Namespace** – a string identifier associated with each item in the theme allowing it to be plugged into. These identifiers are formatted as paths, and may consist of (1) the name of the module, (2) the component name and file and (3) the name of the item. For example, a valid namespace might be `Component/Image/Component`. To assign a namespace to an item, prefix it with a comment: `/** @namespace Component/Image/Component **/`.

**Extension Plugin File** – a file plugging into a theme's functionality by wrapping around some of its functions, classes or their members. An extension can consist of multiple plugins, located in `src/plugin`. Plugin filenames must end with `.plugin.js` (example: `ConfigQuery.plugin.js`).\
\
**Plugin Function** – a function in an Extension Plugin that modifies the behavior of an item in the original theme by wrapping around it. The function, property or class that it plugs into is referred to as the **target function**, **target property** or **target class**, respectively.

**Target Namespace** – the namespace a plugin plugs into.

**Plugin Configuration Object** – the default export of a plugin file; an object specifying the namespaces the plugin targets, and configuring plugin functions for them.

**Plugin Target Type** – in the plugin configuration object, a string specifying the plugin type. For example, class methods can be plugged into with type `member-function` plugins. Class properties can be plugged into with type `member-property` plugins


# Working With Magento


# Magento troubleshooting

Resolving common issues after a Magento+ScandiPWA installation

In general, many Magento problems can be resolved by executing the following Magento commands:

```bash
# in general
magento c:f

# for all issues involving data of the products,
# reviews not being up to date (after change)
magento in:rei

# when Magento 2 module does appear (or function properly)
magento se:up
magento module:enable <MODULE>

# missing classes, issues with Interceptors
rm -rf generated
magento c:f
```

## 404 Not Found on Homepage

There can be multiple reasons why the homepage shows a `404` page. This is usually due to a Magento misconfiguration:

1. Go to *Stores > Configuration > General > Web > Default Pages > CMS Home Page* and check if it is set
2. Go to *Content > Pages* make sure the column `Store View` is not empty for your Home Page CMS page. If it is empty, click on the page, select necessary stores and click save

If nothing has changed and you still see a **404 Not Found** error, try running the following Magento commands:

```bash
magento setup:upgrade
magento setup:di:compile
```

## Luma Theme Visible

If you installed ScandiPWA, but the frontend still displays the Luma theme, follow the steps below.

{% hint style="warning" %}
Verify that the `type` of your theme in the `theme` Magento table to `4`.&#x20;
{% endhint %}

#### Verify that the theme is compiled

Verify that `Magento_Theme` is not be empty, and contains 2 folders. If this is not the case, compile the theme:

{% tabs %}
{% tab title="yarn (Recommended)" %}

```
BUILD_MODE=magento yarn build
```

{% endtab %}

{% tab title="npm" %}

```bash
BUILD_MODE=magento npm run build
```

{% endtab %}
{% endtabs %}

After the command's execution, the folders should appear. If there is a compliation issue, please read the logs to found out why.

#### Verify that the theme is set in the Magento admin panel

Check the *Content > Design > Themes* and make sure your store has the correct ScandiPWA theme set.

#### Flush caches

```bash
magento cache:flush
```

## Asking for Help

If you couldn't resolve your issue, feel free to ask for help! Please see the [guidelines for asking for help](/about/support#asking-for-help) in Slack.


# Working with Magento modules

When developing Magento 2 back-end, all the functionality you would like to add must be located in a module. Magento 2 module may contain:

* Changes to admin looks
* Modifications to Rest API, GraphQL endpoints
* Modifications to Magento 2 routes (**not ScandiPWA routes**)
* [And more!](https://devdocs.magento.com/guides/v2.4/architecture/archi_perspectives/components/modules/mod_intro.html#arch-modules-overview)

{% hint style="danger" %}

### Heads up!

Do not modify the `vendor` folde&#x72;*\** (a place where Magento 2 logic appears to be), this folder is auto-generated for every user, any modifications will be erased during the next `composer install`.\
\
*\*  you can do it for debugging, but do not forget to revert changes*
{% endhint %}

## Creating Magento 2 modules

There are two main ways to create a module in Magento 2:

* [Manually in `app/code`](/developing-with-scandi/working-with-magento/working-with-magento-modules#creating-modules-in-app-code) - for project-specific modules
* [Symlinking into `vendor` using composer](/developing-with-scandi/working-with-magento/working-with-magento-modules#symlinking-with-composer) - for modules you intend to share

After that, you might need to create an initial file structure for your module. Take a look at [the official guide](https://devdocs.magento.com/videos/fundamentals/create-a-new-module/#make-sure-you-have-permission-to-create-files-and-folders-in-your-installation) to learn how to do that!

### Creating modules in `app/code`

If you are building a module to be used by a single project, you can start by creating a new folder using a pattern `<VENDOR>/<NAME>` in `app/code` folder. For example: `app/code/MyProject/MyModule`.

### Symlinking with composer

If you are planning to share this module with other developers on [ScandiPWA Marketplace](https://marketplace.scandipwa.com/) or [Magento Marketplace](https://marketplace.magento.com/) (or even within your company), opt-in to this approach. With it, you would need to define a `composer.json` for your package, and you might create it in any directory of your Magento 2 root (we prefer `localmodules` for example). Then, you need to symlink the package:

```bash
composer config repo.<MODULE NAME> add <PATH TO MODULE>
composer require <"name" FIELD FROM composer.json FILE>
```

{% hint style="info" %}
This approach is more complex for beginners. If you do not feel strong with `composer` it is probably better to create a module in `app/code` first, and then convert your module into a Composer one. See [this guide](https://devdocs.magento.com/guides/v2.4/extension-dev-guide/package/package_module.html) for more details.
{% endhint %}

## Installing Magento 2 modules

Again, there are two ways:

* Composer way
* The ZIP way

After the installation, the following commands must be executed to enable and run post-install scripts of the extensions:

```bash
# see registration.php file to get Magento module name
magento module:enable <MAGENTO MODULE NAME>
magento setup:upgrade
```

### Composer way of installing Magento 2 extensions

That's a very simple operation, which requires a single command:

```bash
composer require <COMPOSER PACKAGE NAME>
```

{% hint style="danger" %}

### Heads up!

If installing extensions from any Marketplace, or other [private composer repositories](https://getcomposer.org/doc/articles/authentication-for-private-packages.md#authentication-for-privately-hosted-packages-and-repositories) - you must make sure your credentials are valid and set. CMA uses `COMPOSER_AUTH` environmental variable to authenticate, but there are [more ways to set the credentials](https://getcomposer.org/doc/articles/authentication-for-private-packages.md#authentication-using-the-composer-auth-environment-variable). **Usually, these credentials are provided by the marketplace in my account section.**
{% endhint %}

### Installing Magento 2 extensions with ZIP archives

All you need to do is extract the ZIP archive of an extension to `app/code` direcory of your Magento 2 project. Make sure the created folder matches a pattern `<VENDOR>/<NAME>`. For example: `app/code/OtherVendor/OtherModule`.


# Working with GraphQL

ScandiPWA requests the data it needs from Magento via GraphQL. By default, Magento already defines a GraphQL schema and corresponding resolvers, enabling the frontend to request certain data via GraphQL. However, the API provided by Magento is not complete, so ScandiPWA has a set of custom modules that extend it to serve a broader range of data.

If you need develop custom backend functionality in your site, or if you want to adapt existing functionality to ScandiPWA, you will need to make sure it can be accessible via GraphQL.

{% hint style="info" %}
Technically, you could fetch data using a REST API as well, but this has several disadvantages and is discouraged [in favor of GraphQL](/introduction/challenges#api-complexity). The ScandiPWA codebase entirely uses GraphQL.
{% endhint %}

## Defining a Query

A GraphQL schema describes what types of data the GraphQL server deals with, what queries and mutations it provides, as well as the expected parameters and responses of those queries.

{% hint style="info" %}
Queries and Mutations in GraphQL are like GET and POST request endpoints in REST – they implement one specific functionality of the API. A key difference is that all queries and mutations are done through one actual endpoint, managed by GraphQL. This allows for more flexibility. You can read an [introduction to GraphQL](https://graphql.org/learn/) if you are not familiar with it.
{% endhint %}

In Magento, you can extend the schema by creating a GraphQL schema file, at `etc/schema.graphqls` in your module. The schema files from all modules get merged together to producs the final schema, accessible to the frontend. This means that you can define new queries or types, as well as extend existing types.

Consider this example [schema](https://graphql.org/learn/schema/):

{% code title="scandipwa/catalog-graphql/src/etc/schema.graphqls (annotated excerpt)" %}

```graphql
# this block contains the queries we define in this schema file
type Query {
    category (
        id: Int @doc(description: "Ids of the category")
        url_path: String @doc(description: "Url path of the category")
    ): CategoryTree
    @resolver(class: "ScandiPWA\\CatalogGraphQl\\Model\\Resolver\\CategoryTree")
}

# this type is already defined elsewhere; we merely want to add
# another field to it
type CategoryTree {
    is_active: Boolean @doc(description: "Category is enabled")
}

```

{% endcode %}

{% hint style="info" %}
In GraphQL, types followed by an exclamation mark `!` are required. Since we have specified `id: Int` and not `id: Int!`, neither of the parameters are required.
{% endhint %}

This schema defines a new GraphQL query that accepts either an `id` or `url_path` and returns a category. The CategoryTree type is already defined elsewhere fields representing a category, so we don't need to re-define that. We do, however, want to add a new field to specify if the category is enabled.

## Implementing a Resolver

A GraphQL resolver is a piece of PHP code that actually implements the queries defined in the Schema. It takes the query parameters as function arguments, and must return a PHP array or associative array of the shape specified in the schema.

The query defined in the schema above specifies a resolver class using the `@resolver` annotation. The Magento GraphQL mechanism will use this class whenever a `cmsPage` query is made.

By convention, resolvers are located in the `Model/Resolver` directory of the module. All resolvers must implement the `Magento\Framework\GraphQl\Query\ResolverInterface` to ensure they are able to work with the GraphQL system.

Here is the resolver used by the category query:

{% code title="scandipwa/catalog-graphql/src/Model/Resolver/CategoryTree.php (simplified)" %}

```php
class CategoryTree implements ResolverInterface
{
    // [...] field declarations, constructor using dependency injection

    /**
     * @inheritdoc
     */
    public function resolve(
        Field $field,
        $context, // some context info (e.g. the user ID if any)
        ResolveInfo $info,
        array $value = null,
        array $args = null // an associative array of the parameters passed
    ) {
        $rootCategoryId = $this->getCategoryId($args);

        // a bunch of helper functions.
        // $category ends up being an array-like value with the data we want
        $categoriesTree = $this->categoryTree->getTree($info, $rootCategoryId);
        $result = $this->extractDataFromCategoryTree->execute($categoriesTree);
        $category = current($result);

        return $category;
    }
}

```

{% endcode %}

When writing most simple resolvers, you will need to know:

* `$context` contains some information about the user's session
* `$args` contain the arguments passed to the query/mutation
* The function must return an array containing the data you want the query to return

## Testing the Query

You can use a tool such as [Altair GraphQL](https://altair.sirmuel.design/) or another GraphQL client to check that your query is working properly. The GraphQL endpoint, by default, is `<your-server>/graphql`.

If your query works as expected, you can now [use it on the frontend](/structure/building-blocks-summary/constructing-graphql-queries)!

## Extending Existing Resolvers

Resolvers, like any other Magento class, can be extended using [Magento Plugins (interceptors)](https://devdocs.magento.com/guides/v2.4/extension-dev-guide/plugins.html), or the DI preference mechanism. You can also extend the GraphQL types of their responses to specify that they now return some new field as well.


# GraphQL Security

Webonyx-GraphQL provides query analysis to reject complex queries to your GraphQL server. This is used to protect GraphQL servers against resource exhaustion and DoS attacks.

## Query Complexity

Query complexity is one of the possible ways to reject GraphQL requests. The idea is to define how complex each field is by using a simple number. By default, each field has a query complexity of **1**.&#x20;

```graphql
type StoreList {           # complexity: 1
    name: String           # complexity: 1
    is_active: Boolean     # complexity: 1
    base_link_url : String # complexity: 1
    base_url : String      # complexity: 1
    code: String           # complexity: 1
}
```

In the example, we can see that if the request contains all fields from type `StoreList`, then it will consume **6** of query complexity. For this case, if the GraphQL server is set to limit query complexity to **3,** it will return a response with the message: "*Max query complexity should be 3 but got 6*."

### How to increase Query Complexity limits

{% hint style="danger" %}
It is strongly recommended to not increase Query Complexity limits because it will weaken the security of your website.
{% endhint %}

Sometimes we need to get new fields in the same request to implement a new feature on the website. This leads to situations where it would be very complex to implement the feature without increasing limits. In this case, it is necessary to slightly increase the limit in the Query Complexity rule.\
\
In ScandiPWA, the Query Complexity rule number is set in the [ScandiPWA\_CatalogGraphQL](https://github.com/scandipwa/catalog-graphql) module. It can be checked in the file *'/src/etc/di.xml' .*

```markup
<type name="Magento\Framework\GraphQl\Query\QueryComplexityLimiter">
    <arguments>
        <argument name="queryComplexity" xsi:type="number">VALUE</argument>
    </arguments>
</type>
```

&#x20;To increase limits, we need to create Magento module and extend [ScandiPWA\_CatalogGraphQL](https://github.com/scandipwa/catalog-graphql) where it will be possible to set a new rule.

{% hint style="info" %}
If the project already includes an extended [ScandiPWA\_CatalogGraphQL](https://github.com/scandipwa/catalog-graphql), it is better to increase the query complexity value there.&#x20;
{% endhint %}

**Steps to create and increase limits:**\
\
1\. [Create a simple Magento module](https://devdocs.magento.com/videos/fundamentals/create-a-new-module/) with the name "**GraphQLQueryComplexity**" or any other you wish.\
2\. In the file *'etc/module.xml'* , add  [ScandiPWA\_CatalogGraphQL](https://github.com/scandipwa/catalog-graphql) as below:

```markup
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="ScandiPWA_GraphQlQueryComplexity">
        <sequence>
            <module name="ScandiPWA_CatalogGraphQl"/>
        </sequence>
    </module>
</config>

```

3\. Create the file *'etc/di.xml'*  where the Query Complexity rule will be changed.

```markup
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\GraphQl\Query\QueryComplexityLimiter">
        <arguments>
            <argument name="queryComplexity" xsi:type="number">YOUR VALUE</argument>
        </arguments>
    </type>
</config>
```

### How to troubleshoot

To get beyond Query Complexity Rule limits, it is necessary to detect this problem at the stage of developing the project. By default, Magento enables query complexity rule only on production mode, which means that when the project is in development mode GraphQL won't reject a request even if it is above the set rule.

For this reason, ScandiPWA changes Magento's default behavior by enabling query complexity rule in development mode and showing query complexity in each response that the user sends to the server.

{% hint style="info" %}
Starting only from version  "**3.0.2**" of module [ScandiPWA\_PersistedQuery](https://github.com/scandipwa/persisted-query) is it possible to see query complexity on request and enabled rule in development mode.
{% endhint %}

\
To see how much Query complexity is requested, we need to open browser tools and check the response of the request. In the section of "Response Headers", it is easy to find a field with the name "query-complexity" where its value is the "cost" of your request.

![GraphQl Response Headers](/files/-MlVTTKLGWkXs3ES0EKr)

This will help to check the query complexity before getting errors about it and see how close it is to limits.<br>


# Working with "granular cache"

ScandiPWA controls cache in a different way than [default Magento 2](https://devdocs.magento.com/guides/v2.3/graphql/develop/create-graphqls-file.html#query-caching) does. We are still using the caching identities, but, instead of specifying them on GraphQL queries, we use events.

## General rule

To make some of your model work as cache identity manager:

1. Make sure it implements the `Magento\Framework\DataObject\IdentityInterface`

```php
use Magento\Framework\DataObject\IdentityInterface;
use Magento\Framework\Model\AbstractModel;

class Slide extends AbstractModel implements IdentityInterface {
    public function getIdentities() {
        // TODO: implement
    }
}
```

2\. Specify the caching tag constant, it should be short and unique:

```php
class Slide extends AbstractModel implements IdentityInterface {
    const CACHE_TAG = 'sw_sld';

    protected $_cacheTag = self::CACHE_TAG;
}
```

3\. Implement the `getIdentities` method, specify all involved cache identities. In our example, on slide save, the slider model should also be invalidate:

```php
class Slide extends AbstractModel implements IdentityInterface {
    const CACHE_TAG = 'sw_sld';

    public function getIdentities() {
        return [
            self::CACHE_TAG . '_' . $this->getId(),
            Slider::CACHE_TAG . '_' . $this->getSliderId()
        ];
    }
}
```

4\. Add the names for events, prefer unique, descriptive names:

```php
class Slide extends AbstractModel implements IdentityInterface {
    protected $_eventPrefix = 'scandiweb_slider_slide';
}
```

5\. In case you have a resource model, i.e. the `Collection`, add the event after collection save:

```php
use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;

class Collection extends AbstractCollection {
    protected function _afterLoadData() {
        parent::_afterLoadData();

        $collection = clone $this;

        if (count($collection)) {
            $this->_eventManager->dispatch(
                'scandiweb_slider_slider_collection_load_after',
                ['collection' => $collection]
            );
        }

        return $this;
    }
}
```

6\. It is finally time to connect the events, to granular cache management classes. Create, or modify the `etc/events.xml` with the following content:

```php
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
    <event name="scandiweb_slider_slider_collection_load_after">
        <observer name="pq_cc_slider" instance="ScandiPWA\Cache\Observer\Response\TagEntityResponse"/>
    </event>
    <event name="scandiweb_slider_slider_save_after">
        <observer name="pq_cc_slider" instance="ScandiPWA\Cache\Observer\FlushVarnishObserver"/>
    </event>
    <event name="scandiweb_slider_slide_collection_load_after">
        <observer name="pq_cc_slide" instance="ScandiPWA\Cache\Observer\Response\TagEntityResponse"/>
    </event>
    <event name="scandiweb_slider_slide_save_after">
        <observer name="pq_cc_slide" instance="ScandiPWA\Cache\Observer\FlushVarnishObserver"/>
    </event>
</config>
```

Note, there are two classes used as observers:

* `ScandiPWA\Cache\Observer\FlushVarnishObserver` - responsible for flushing, must be triggered on save of the model.
* `ScandiPWA\Cache\Observer\Response\TagEntityResponse` - responsible for tagging, must be triggered after load of the collection/model.


# Developer Tools

To make the development process easier, we have compiled a set of tools for working with Scandi

## IDE

You will need an integrated development environment (IDE) – this is where you edit code and manage files. If you already have a favorite editor, feel free to use that. If not – we recommend [VS Code](https://code.visualstudio.com/).

## Browser

You're making a web app, so you need a browser to test your code! We recommend Google Chrome or Firefox. To help with debugging, we also recommend the following browser extensions:

| Plugin                                                                     | Firefox                                                                 | Chrome                                                                                                              |
| -------------------------------------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| React Developer Tools - allows you to inspect the React element hierarchy. | [Addon](https://addons.mozilla.org/en-US/firefox/addon/react-devtools/) | [Extension](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en) |
| Redux DevTools - for inspecting the Redux state and actions                | [Addon](https://addons.mozilla.org/en-US/firefox/addon/reduxdevtools/)  | [Extension](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=en)        |

{% hint style="info" %}
When developing, the Scandi "new version" popup may get annoying, if you are using Google Chrome, you can go to **Developer Tools > Application > Service Workers** and enable the **Bypass for network** checkbox.
{% endhint %}

## ScandiPWA CLI

Some tasks can get tedious when developing with Scandi. We wrote a command-line tool to automate them! Try the [ScandiPWA CLI](/developing-with-scandi/developer-tools/scandipwa-cli) – it will save you a lot of time.


# Debugging in VSCode

This guide only works for Visual Studio Code

To start debugging, you must complete 3 steps sequence:

1. [Install VSCode extension](/developing-with-scandi/developer-tools/debugging-in-chrome#1-install-a-vscode-extension)
2. [Configure VSCode debugger](/developing-with-scandi/developer-tools/debugging-in-chrome#2-configure-vscode-debugger)
3. [Launch the debugger](/developing-with-scandi/developer-tools/debugging-in-chrome#3-launch-the-debugger)

## 1. Install a VSCode extension

Install following extensions for VSCode:

{% tabs %}
{% tab title="Firefox" %}
Install "[Debugger for Firefox](https://marketplace.visualstudio.com/items?itemName=firefox-devtools.vscode-firefox-debug)" extension.
{% endtab %}

{% tab title="Chrome" %}
Install "[Debugger for Chrome](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome)" extension.
{% endtab %}
{% endtabs %}

## 2. Configure VSCode debugger

{% tabs %}
{% tab title="Firefox" %}
Open a file you wish to debug in Visual Studio Code. In the side panel, open the ***Run*** tab. Click ***create a launch.json file***, then select the ***Firefox*** option. Configure the generated file as follows:

```javascript
{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "chrome",
            "request": "firefox",
            "reAttach": true,
            "name": "Launch Firefox against localhost",
            "url": "http://localhost:3000/",
            "webRoot": "${workspaceFolder}"
        }
    ]
}
```

{% endtab %}

{% tab title="Chrome" %}
Open a file you wish to debug in Visual Studio Code. In the side panel, open the ***Run*** tab. Click ***create a launch.json file***, then select the ***Chrome*** option. Configure the generated file as follows:

```javascript
{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "chrome",
            "request": "launch",
            "name": "Launch Chrome against localhost",
            "url": "http://localhost:3000/",
            "webRoot": "${workspaceFolder}"
        }
    ]
}
```

{% endtab %}
{% endtabs %}

Set `url` to the URL of your app, most likely <http://localhost:3000/>. Other settings can remain the same.

## 3. Launch the debugger

Click "Launch Chrome against localhost" in the "Run" tab. You should now be able to pause at breakpoints and view the console output.

## Troubleshooting

{% hint style="danger" %}
\[debugger for chrome] Error processing "launch": Can't find Chrome
{% endhint %}

This can occur if the debugger expects a different executable than the one you have installed. For example, you might have `google-chrome-stable` or Chromium installed instead of the more common Chrome.

In this case, you can specify which executable the extension should use by setting `runtimeExecutable` in the JSON config.

```
{
    "configurations": [
        {
            // [...]
            "runtimeExecutable": "/snap/bin/chromium"
        }
    ]
}
```


# ScandiPWA CLI

A utility for accelerating development with Scandi

Example – with one command, create a component template in src/component/HugeTitle:

```
scandipwa create component HugeTitle
```

A [VSC plugin for Scandi CLI](https://marketplace.visualstudio.com/items?itemName=ScandiPWA.scandipwa-development-toolkit-vscode) is also available!

## Installation

Install the npm package globally:

```
npm i -g scandipwa-cli
```

## Usage

The CLI must be run from the the Scandi theme directory or a subdirectory.

Global options:

* `--help` to get usage help
* `--version` to print version number and exit

### `create component`

Creates a new [Scandi component](https://docs.scandipwa.com/structure/building-blocks-summary/components)

Syntax:

```
create component [--container] [--redux] <name>
```

Options:

* `--container`/`-c` creates a container file for the component
* `--redux`/`-r` connects the component to the Redux store with the `connect` HOC
* `name` is the name of the component to be created

Examples:

```
scandipwa create component HugeTitle
# Output:
NOTE!

     The following files have been created:
     src/component/HugeTitle/HugeTitle.component.js
     src/component/HugeTitle/HugeTitle.style.scss
     src/component/HugeTitle/index.js
```

```
scandipwa create component --container SpecialHeader
# Output:
NOTE!

     The following files have been created:
     src/component/SpecialHeader/SpecialHeader.component.js
     src/component/SpecialHeader/SpecialHeader.style.scss
     src/component/SpecialHeader/index.js
     src/component/SpecialHeader/SpecialHeader.container.js
```

### `create route`

Creates a new [Scandi route](https://docs.scandipwa.com/structure/building-blocks-summary/routes)

Syntax:

```
create route [--container] [--redux] <name>
```

Options:

* `--container`/`-c` creates a container file for the route
* `--redux`/`-r` connects the route to the Redux store with the `connect` HOC
* `name` is the name of the route to be created

Example:

```
scandipwa create route MyLandingPage
# Output:
NOTE!

     The following files have been created:
     src/route/MyLandingPage/MyLandingPage.component.js
     src/route/MyLandingPage/MyLandingPage.style.scss
     src/route/MyLandingPage/index.js
```

### `create store`

Creates a new [Scandi Redux store](https://docs.scandipwa.com/structure/building-blocks-summary/redux-stores)

Syntax:

```
create store [--dispatcher-type=<"no"|"regular"|"query">] <name>
```

Options:

* `--dispatcher-type`/`-d` determines what type of dispatcher file will be created.
  * `no` (default) - does not create a dispatcher
  * `regular` - creates a simple helper class for dispatching actions
  * `query` - creates a dispatcher that `extends QueryDispatcher`
* `name` is the name of the store to be created

Example:

```
scandipwa create store --d=query UserPreferences
# Output:
NOTE!

     The following files have been created:
     src/store/UserPreferences/UserPreferences.action.js
     src/store/UserPreferences/UserPreferences.dispatcher.js
     src/store/UserPreferences/UserPreferences.reducer.js
```

### `create query`

Creates a new [Scandi query helper](https://docs.scandipwa.com/structure/building-blocks-summary/constructing-graphql-queries) for querying with GraphQL

Syntax:

```
create query <name>
```

`name` is the name of the query to be created

Example:

```
scandipwa create query Weather
# Output:
NOTE!

     The following files have been created:
     src/query/Weather.query.js
```

### `deploy`

Deploys your app to the cloud

Syntax:

```
deploy
```

Example

```
 scandipwa deploy
yarn run v1.22.5
$ scandipwa-scripts build
Creating an optimized production build...
Build completed in 186.317s

Compiled successfully.
[...]
Done in 189.38s.
Build files compressed successfully.
Code upload result: OK. Code: 200
Build archive successfully removed.
Congrats, your code will be deployed in a few minutes! You can access it here: https://master.d16zgbgmy9fzgx.amplifyapp.com/
```

### `extension install`

Installs a Scandi extension

Syntax:

```
extension install [--no-enable] [--local] [--use=<path>] [--version=<required-version>] [--save-dev] <name>
```

Options:

* `--no-enable` will install the extension without enabling it
* `--local`/`-l`: use a local module from `packages/<name>`
* `--use`/`-u`: use a local module from the specified `<path>`
* `--version`/`-v` specifies the extension version to use
* `save-dev`/`-D`: install the package as a devDependency
* `name` is the name of the extension to install

### `extension create`

Creates a new scandi extension

Syntax:

```
extension create [--no-enable] <name>
```

Options:

* `--no-enable` will create and install the extension without enabling it
* `name` specifies the name of the new extension

### `override component`

Overrides a [Scandi component](https://docs.scandipwa.com/structure/building-blocks-summary/components). Will interactively ask for which parts to override.

Syntax:

```
override component [--styles=<"extend"|"override"|"keep">] [--source-module=<module>] [--target-module=<module>] <name>
```

Options:

* `--styles`/`-S`:
  * Not specified (default): will prompt interactively
  * `keep`: don't override styles
  * `extend`: adjust existing styles
  * `override`: completely replace existing styles
* `--source-module`/`-s`: Path to the module to override the component from
* `--target-module`/`-t`: Path to the module to generate the component in&#x20;
* `name` is the name of the component to be overridden

Example:

```
scandipwa override component Header
? Choose things to extend in Header.component.js Header
? What would you like to do with styles? Extend
? Choose things to extend in Header.config.js 
? Choose things to extend in Header.container.js 

NOTE!

     The following files have been created:
     src/component/Header/Header.override.style.scss
     src/component/Header/Header.component.js
```

### `override route`

Overrides a [Scandi route](https://docs.scandipwa.com/structure/building-blocks-summary/routes)

Syntax:

```
override route [--styles=<"extend"|"override"|"keep">] [--source-module=<module>] [--target-module=<module>] <name>
```

Options:

* `--styles`/`-S`:
  * Not specified (default): will prompt interactively
  * `keep`: don't override styles
  * `extend`: adjust existing styles
  * `override`: completely replace existing styles
* `--source-module`/`-s`: Path to the module to override the route from
* `--target-module`/`-t`: Path to the module to generate the route in&#x20;
* `name` is the name of the route to be overridden

### `override store`

Overrides a [Scandi Redux store](https://docs.scandipwa.com/structure/building-blocks-summary/redux-stores)

Syntax:

```
override store [--source-module=<module>] [--target-module=<module>] <name>
```

Options:

* `--source-module`/`-s`: Path to the module to override the store from
* `--target-module`/`-t`: Path to the module to generate the store in&#x20;
* `name` is the name of the store to be overridden

### `override query`

Overrides a [Scandi query helper](https://docs.scandipwa.com/structure/building-blocks-summary/constructing-graphql-queries)

Syntax:

```
override query [--source-module=<module>] [--target-module=<module>] <name>
```

Options:

* `--source-module`/`-s`: Path to the module to override the query from
* `--target-module`/`-t`: Path to the module to generate the query in&#x20;
* `name` is the name of the query to be overridden


# Configuring ESLint

The **ScandiPWA out of the box comes with a very strict linter**. It is here to ensure the quality and consistency of the code between projects. In some cases, the configured defaults might seem too strict, this guide is here to help developers configure to match their needs.

Some rules are our preference, some are "essential" to make sure the code you are writing is compatible with ScandiPWA's plugin architecture. The list of such rules can be found below.

## Essential rules

All `scandipwa` specific rules can be found in `@scandipwa/eslint-plugin-scandipwa-guidelines` NPM package. To use it in your ESLint configuration, add the following fields to your declaration:

```javascript
{
    "plugins": [
        "@scandipwa/scandipwa-guidelines"
    ],
    "rules": {
        // Force @namespace comments in the code
        "@scandipwa/scandipwa-guidelines/use-namespace": "error",
        // Use "__construct" instead of "constructor"
        "@scandipwa/scandipwa-guidelines/use-magic-construct": "error",
    }
}
```

## How to disable ESLint

By default, ESLint is always enabled and check on every compilation. This ensures the quality of code before committing it (otherwise if checked on commit, the changes made are commonly not checked by the developer on a working site).

To disable the ESLint, the following configuration must be added to the ScandiPWA theme's `package.json` file:

```javascript
{
        ...
        "eslintConfig": {
                "extends": "@scandipwa",
                "ignorePatterns": ["src/**"]
        },
        ...
}
```

Learn more about `ignorePatterns` in [official ESLint docs](https://eslint.org/docs/user-guide/configuring/ignoring-code).

## How to change ESlint configurations

You have two main options to change (learn about more options in [the official ESLint guide](https://eslint.org/docs/user-guide/configuring/configuration-files#using-configuration-files)):

* Extend a completely different preset
* Add "overrides" to default configuration values

To change any of these, the ScandiPWA theme's `package.json` file's `eslintConfig` fields. For example:

```javascript
{
    ...
    "eslintConfig": {
        "extends": [
            "react-app"
        ],
        "overrides": [
            "react/jsx-props-no-spreading": "off"
        ]
    }
    ...
}
```


# CSA Commands

## `npm start` or `yarn start`

Runs the app in development mode. Will open the [http://localhost:3000](http://localhost:3000/) to preview changes in your default browser.

{% hint style="info" %}
The page will automatically reload if you make changes to the code. You will see the build errors and lint warnings in the console.
{% endhint %}

## `npm run build` or `yarn build`

Builds the app for production to the `build` folder. It correctly bundles React in production mode and optimizes the build for the best performance. The build files will be minified and the filenames will include the hashes. Your app is ready to be deployed.

{% hint style="info" %}
Looking for CMA commands? Please refer to the [CMA documentation](https://docs.create-magento-app.com/getting-started/available-commands).
{% endhint %}


# Deploying Your App

{% content-ref url="/pages/-MYy9SCRvvm4ZRFTs9ZO" %}
[Build & Deploy iOS app](/developing-with-scandi/deploying-your-app/build-and-deploy-ios-app)
{% endcontent-ref %}

{% content-ref url="/pages/-MYyMHPmuDQgLaQiumQu" %}
[Build & Deploy Android app](/developing-with-scandi/deploying-your-app/build-and-deploy-android-app)
{% endcontent-ref %}


# Build & Deploy Android app

You can package Scandi as a native Android application

Android made it simple to publish web-wrapper-based apps to the marketplace. But, it also provides a little bit more security by introducing the concept of [**Trusted Web Activity**](https://developer.chrome.com/docs/android/trusted-web-activity/overview/).

## Building an app

### 1. Create a Developer Account

Visit [**the official site**](https://play.google.com/console/u/0/signup) and register for a developer account.

### 2. Clone ScandiPWA Android app template

You can download ZIP for [this repository](https://github.com/scandipwa/scandipwa-android-app), or clone with a command below:

```
git clone git@github.com:scandipwa/scandipwa-android-app.git
```

### 3. Change the site URL in `MainActivity.java`

Replace <https://demo.scandipwa.com/> with your site's URL.

### 4. Replace the package name with the one you want

Find and replace all `com.scandipwa` of this String in a cloned repository and replace it with your custom value.&#x20;

### 5. Verify your site by uploading a file

Upload the file of a similar structure to your server's `.well-known/assetlinks.json` URL. **Make sure it's publicly accessible!**

```
[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target" : { "namespace": "android_app", "package_name": "<PACKAGE_NAME>",
               "sha256_cert_fingerprints": ["<FINGERPRINT>"] }
}]
```

Make sure to replace `PACKAGE_NAME` with the one, you chose in [**step 4**](/developing-with-scandi/deploying-your-app/build-and-deploy-android-app#4-replace-the-package-name-with-the-one-you-want).

`FINGERPRINT` with values obtained from your **Google Play Console > Release Management > App Signing**.

## Publishing the app

{% hint style="warning" %}
You will need an Android Studio installed. You can[ download it here](https://developer.android.com/studio).
{% endhint %}

1. Open the project, and navigate to **Build -> Generate Signed Bundle / APK**.
2. Choose **APK**,  select (and create) key store file, enter passwords
3. Choose **release** build variant and check both **Signature versions**
4. Go to your [developer console](https://play.google.com/console), navigate to **All applications**
5. Click **Create Application** and enter required fields
6. Prepare description and screenshots ([requirements](https://support.google.com/googleplay/android-developer/answer/9866151?visit_id=637547733930810820-3881892387\&rd=1)) for your app
7. Navigate to **Release management > App releases**
8. Select the `.apk` file created on step 3
9. Select your release and click **Review**

For a more detailed guide, please [see The Manifests's guide](https://themanifest.com/mobile-apps/how-publish-app-google-play-step-step-guide).

The process of review might take up to 7 days and result in refusal. ScandiPWA does not guarantee your app publishing. Please make sure the app you build complies with [**PlayStore guidelines**](https://play.google.com/about/developer-content-policy/).


# Build & Deploy iOS app

You can package Scandi as a native iOS application

To deploy an iOS app, you need to have at least one native feature to be included in the app. We used a barcode scanner to search for items. You can add notifications management or anything else, but you have to add it as native code.

{% hint style="warning" %}
**To deploy an iOS application you must have a macOS-powered device.**
{% endhint %}

To display your site in iOS App, you first need to make sure it is deployed online. Then, you can use WebView to display the site inside of an iOS native app. To integrate native feature to the site, we suggest using the following approach:

1. Create a Swift View Controller
2. Display WebView inside of this controller
3. Create a JavaScript file that would be injected into the app
4. Use WebKit message handlers API to establish communication between WebView and native app

## Creating an app (step-by-step)

### 1. Create a new XCode project

![](/files/-MYyCBlJxp3UlkmMFyu8)

### 2. Select single view template

![](/files/-MYyCXbbDuo-KBRSbrve)

### 3. Go to `ViewController.swift`&#x20;

![](/files/-MYyCkUnaDNiGLgsv-Ej)

### 4. Replace its content with the script bellow

```swift
import UIKit
import WebKit

class ViewController: UIViewController, WKUIDelegate, WKScriptMessageHandler {
    /// Assuming that the javascript sends message back, this function handles the message
    ///
    /// - Parameters:
    ///   - userContentController: controller
    ///   - message: Message. Can be a String or [String:Any] to a single level.
    func userContentController(
        _ userContentController: WKUserContentController,
        didReceive message: WKScriptMessage
    ) {
        print("something recived");

        let messageBody = message.body as! [String: Any];
        let action = messageBody["action"] as! String;
            
        switch action {
        case "barcodeClick":
            print("barcode was clicked");
            return;
        default:
            return;
        }
    }
    
    lazy var webView: WKWebView = {
        let   webCfg:WKWebViewConfiguration = WKWebViewConfiguration()
        
        // Setup WKUserContentController instance for injecting user script
        var userController:WKUserContentController = WKUserContentController()
        
        var script:String?
        
        // Get the contents of the file `inject.js`
        if let filePath:String = Bundle.main.path(forResource: "inject", ofType:"js") {
            script = try! String(contentsOfFile: filePath, encoding: .utf8)
        }
        
        let userScript:WKUserScript =  WKUserScript(source: script!, injectionTime: WKUserScriptInjectionTime.atDocumentStart, forMainFrameOnly: false)
        
        userController.addUserScript(userScript)
        
        // Add a script message handler for receiving  "nativeProcess" event notifications posted from the JS document using window.webkit.messageHandlers.nativeProcess.postMessage script message
        userController.add(self, name: "nativeProcess")
        
        // Configure the WKWebViewConfiguration instance with the WKUserContentController
        webCfg.userContentController = userController;
        
        let webView = WKWebView(
            frame: CGRect(
                x: 0,
                y: 0,
                width: self.view.frame.width,
                height: self.view.frame.height
            ),
            configuration: webCfg
        )
        
        return webView
    }();
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Do any additional setup after loading the view.
        self.navigationItem.title = "ScandiPWA"
        self.view.addSubview(webView)
        let urlToLoad = URL(string: "https://tech-demo.scandipwa.com")
        // Do any additional setup after loading the view.
        webView.load(URLRequest(url: urlToLoad!))
    }
}

```

### 5. Create an `inject.js` file from empty template

![](/files/-MYyDSz2ZtaAS7fvESpS)

![](/files/-MYyDb_xXUa4s5RchXVN)

### 6. Replace its content with the script bellow

```javascript
function sendToNative(message) {
    // Initiate the handle for Native process
    const native = window.webkit.messageHandlers.nativeProcess
    native.postMessage(message)
}

function onBarcodeClick() {
    sendToNative({
         action: 'barcodeClick',
     });
}

const BARCODE_SCANNER_ID = 'barcode-scanner';
const SEARCH_FIELD_ID = 'search-field';

function tryRenderingElement() {
    setTimeout(() => {
               if (document.getElementById(BARCODE_SCANNER_ID)) {
               return;
               }
               
               const searchElement = document.getElementById(SEARCH_FIELD_ID);
               
               const barcodeButton = document.createElement('button');
               barcodeButton.id = BARCODE_SCANNER_ID;
               barcodeButton.style.width = '30px';
               barcodeButton.style.height = '30px';
               barcodeButton.style.marginLeft = '1.4rem';
               barcodeButton.style.backgroundImage = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 480 480'%3E%3Cpath d='M80 48H16C7 48 0 55 0 64v64a16 16 0 0032 0V80h48a16 16 0 000-32zM464 336c-9 0-16 7-16 16v48h-48a16 16 0 000 32h64c9 0 16-7 16-16v-64c0-9-7-16-16-16zM464 48h-64a16 16 0 000 32h48v48a16 16 0 0032 0V64c0-9-7-16-16-16zM80 400H32v-48a16 16 0 00-32 0v64c0 9 7 16 16 16h64a16 16 0 000-32zM64 112h32v256H64zM128 112h32v192h-32zM192 112h32v192h-32zM256 112h32v256h-32zM320 112h32v192h-32zM384 112h32v256h-32zM128 336h32v32h-32zM192 336h32v32h-32zM320 336h32v32h-32z'/%3E%3C/svg%3E")`;
               barcodeButton.style.backgroundSize = 'contain';
               barcodeButton.onclick = onBarcodeClick;
               
               const searchFieldWrapper = searchElement.parentNode.parentNode;
               searchFieldWrapper.style.display = 'flex';
               searchFieldWrapper.style.alignItems = 'center';
               searchFieldWrapper.appendChild(barcodeButton);
               
               const searchField = searchElement.parentNode;
               searchField.style.flexGrow = '1';
               }, 0);
}

const pushState = window.history.pushState;
window.history.pushState = function () {
    // Track page changes in React
    pushState.apply(window.history, arguments);
    tryRenderingElement();
};

tryRenderingElement();
```

### 7. Compile and test the application

![](/files/-MYyE-V1385k5qBMWE63)

![](/files/-MYyE7IqtXzVawTE4lop)

As you can see injecting scripts into WebView is not that hard. When clicking on the barcode icon, the XCode console logs the `barcode was clicked` message. You can replace this logic with anything that matches your needs.

We will publish the full app code (including the barcode implementation) soon, so you can use it as a reference.

When the app is done, it's time to publish it!

## Publishing the app

To publish an iOS app, you must be signed up for the Apple Developer Program. You can do this on the official site, [here](https://developer.apple.com/programs/).&#x20;

1. Login to your [App Store Connect](https://appstoreconnect.apple.com/login)
2. Go to **My Apps** click **+**
3. Enter app name, category, privacy, pricing
4. Make screenshots of your application ([size guide](https://help.apple.com/app-store-connect/#/devd274dd925))
5. Next, build your app with XCode
   1. For build platform select: **Generic iOS Device**
   2. Go to **Product > Archive** and build the app
   3. Select newly created archive, click **Distribute App**, select **iOS** **AppStore**
6. Go back to your [App Store Connect](https://appstoreconnect.apple.com/login)
7. Find a version of your app, and click **Submit for Review**

For a more detailed guide, please [see Chris's guide](https://codewithchris.com/submit-your-app-to-the-app-store/#apple-developer-program).

The process of review might take up to 10 days and result in refusal. ScandiPWA does not guarantee your app publishing. Please make sure the app you build complies with [**AppStore guidelines**](https://developer.apple.com/app-store/review/guidelines/).


# Directory Structure

A high-level overview of how files are organized in Scandi

When creating an app for the first time, the folder structure of the application will look as follows:

```bash
📁  my-app
├── 📄  README.md
├── 📄  composer.json
├── 📁  i18n
├── 📁  magento
|  ├── 📁  etc
|  |  └── 📄  view.xml
|  ├── 📄  registration.php
|  └── 📄  theme.xml
├── 📁  node_modules
├── 📄  package.json
├── 📁  public
├── 📁  src
└── 📄  yarn.lock
```

{% hint style="info" %}
**There `public`and `src` folders are empty**. Do not panic! You will use them to [create overrides](/developing-with-scandi/override-mechanism). The application should compile with them being empty!
{% endhint %}

## ScandiPWA theme `src` structure

ScandiPWA theme (aka. your [parent theme](https://docs.create-scandipwa-app.com/themes/extensions-and-themes#parent-theme)) has the same root folder structure, but much more files in the `src` folder. They are structured as follows:

```bash
📁  src
├── 📁  component # a place for all components
├── 📁  query # a place for GraphQL queries
├── 📁  route # a place for all root pages
├── 📁  store # a Redux store declarations
├── 📁  type # a PropType declarations
├── 📁  util # all utility functions
├── 📄  index.js # application entrypoint
└── 📄  service-worker.js # service worker entrypoint
```

More information about the structure and contents of these folders:

{% content-ref url="/pages/-MNcqDmUflG0I\_EL\_pGb" %}
[Building Blocks](/structure/building-blocks-summary)
{% endcontent-ref %}

## CMA (Create Magento App) structure

CMA structure is similar to the [default Magento folder structure](https://www.mageplaza.com/devdocs/file-structure-magento-2.html), but with a `package.json` file in the root directory. For more details, refer to the [official CMA guide](https://docs.create-magento-app.com/getting-started/folder-structure).


# Building Blocks

Learn about the structure of the ScandiPWA codebase

The ScandiPWA theme is separated into several subdirectories with specific responsibilities. This organization is enforced to ensure that the codebase is consistent and easy to navigate.

* `component`: contains definitions of reusable React components defined throughout the theme

{% content-ref url="/pages/-MNctSfba\_-IE\_yzAELr" %}
[Components](/structure/building-blocks-summary/components)
{% endcontent-ref %}

* `query`: defines a helper class for each GraphQl query that the theme needs

{% content-ref url="/pages/-MO5QlCvRHPOnhdpWRRa" %}
[GraphQL Queries](/structure/building-blocks-summary/constructing-graphql-queries)
{% endcontent-ref %}

* `route`: like `component`, but each route is added to the router, ensuring that it appears as a page in the SPA.

{% content-ref url="/pages/-MNwKSVyvIri6wyH38H\_" %}
[Routes](/structure/building-blocks-summary/routes)
{% endcontent-ref %}

* `store`: defines the global state of the application using Redux stores

{% content-ref url="/pages/-MO0Esw0AOpKiwpb\_KD8" %}
[Redux Stores](/structure/building-blocks-summary/redux-stores)
{% endcontent-ref %}

* `style`: sets the global styles of the application with SCSS

{% content-ref url="/pages/-MOFmOSS8pR629GGQ4Un" %}
[Global Styles](/structure/building-blocks-summary/global-styles)
{% endcontent-ref %}

* `type`: declares JavaScript data structure types using PropTypes

{% content-ref url="/pages/-MOAsI1fsIvYfvTttbBR" %}
[Type Checking](/structure/building-blocks-summary/type-checking)
{% endcontent-ref %}

* `util`: utility classes, functions and constants that do not fall in the other categories

{% content-ref url="/pages/-MOFeRtKoc3zIo06LUI4" %}
[The Util Directory](/structure/building-blocks-summary/the-util-directory)
{% endcontent-ref %}

{% hint style="warning" %}
When [extending the theme](/developing-with-scandi/override-mechanism), it is very strongly encouraged to maintain the same structure. Refrain from adding new directories or nesting directories too deeply.
{% endhint %}


# Components

ScandiPWA React components are reusable pieces of UI logic

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](/structure/building-blocks-summary/components#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](/structure/building-blocks-summary/components#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](/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 %}


# Styling Components

ScandiPWA uses the BEM methodology and SCSS

To style components, ScandiPWA uses the [Block-Element-Modifier (BEM)](https://en.bem.info/methodology/) methodology. BEM is based on assigning meaningful and unique class names to each HTML element we want to style while ensuring that we never need to use any CSS nesting in selectors.

We strive to follow this methodology in the core ScandiPWA theme, and we strongly encourage you to use it when overriding this theme as well.

{% hint style="success" %}
Benefits of folowing our BEM guidelines:

* The codebase is consistent
* Styles are maintainable and can be re-used by composition with mixes
* Styles can be overriden easily in child themes
  {% endhint %}

## The BEM Methodology

{% hint style="info" %}
This section should serve as a quick introduction to how BEM is used in ScandiPWA. You can read the official [BEM Guide](https://en.bem.info/methodology/quick-start/). ScandiPWA uses the [React variation](https://en.bem.info/methodology/naming-convention/#react-style).
{% endhint %}

All primary BEM classes are composed of these 2 parts:

* `<Block>` - in UpperCamelCase, the name of the component this element belongs to
* `-<Element>` (can be left out) - in UpperCamelCase, a meaningful name of this element (e.g. `Container`, `Button`, `Divider`, `Image`... whatever identifies the purpose of this element)

{% hint style="info" %}
Blocks indicate which component owns this element and preserves uniqueness accross all components. By keeping it the same as the component name, you don't need to worry about name-clashes with other components.

The "main" HTML element of each block may be left without a BEM Element. All other elements should have one to indicate their function.
{% endhint %}

In addition to its primary `<Block[-Element]>` class, a block or element may have any number of modifiers of the form:

* `<Block[-Element]>_<booleanModifier>` - for boolean modifiers such as `isActive`, `isVisible`, `isBold`, etc. The presence of this modifier indicates that it is "true", and it's absence indicates that it is "false".
* `<Block[-Element]>_<modifierName>_<someValue>` - for boolean modifiers with values such as `color_red`, `type_primary`, `size_thumbnail`

{% hint style="success" %}
BEM modifiers can be used to indicate state, available actions, or to distinguish between similar instances of the same element.
{% endhint %}

## BEM in JavaScript

Formatting BEM classes manually with `className` would get repetitive, so ScandiPWA uses [`rebem-jsx`](https://github.com/rebem/rebem-jsx) to be able to use `block` and `elem` to specify the class. As an example, look at the `render` method of `ProductCard`:

{% code title="component/ProductCard/ProductCard.component.js (excerpt)" %}

```jsx
    render() {
        const {
            children,
            mix,
            isLoading
        } = this.props;

        return (
            <li
              block="ProductCard"
              mix={ mix }
            >
                <Loader isLoading={ isLoading } />
                { this.renderCardWrapper((
                    <>
                        <figure block="ProductCard" elem="Figure">
                            { this.renderPicture() }
                        </figure>
                        <div block="ProductCard" elem="Content">
                            { this.renderReviews() }
                            { this.renderProductPrice() }
                            { this.renderVisualConfigurableOptions() }
                            { this.renderTierPrice() }
                            { this.renderMainDetails() }
                            { this.renderAdditionalProductDetails() }
                        </div>
                    </>
                )) }
                <div block="ProductCard" elem="AdditionalContent">
                    { children }
                </div>
            </li>
        );
    }
```

{% endcode %}

{% hint style="info" %}
Note that the block is always the same as the name of the component. This ensures consistency and prevents name clashes.
{% endhint %}

To add modifiers, pass an object with modifiers to the `mods` prop. Boolean modifiers will be automatically detected and treated as such.

```jsx
    renderMainDetails() {
        const { product: { name } } = this.props;

        return (
            <p
              block="ProductCard"
              elem="Name"
              mods={ { isLoaded: !!name } }
            >
                <TextPlaceholder content={ name } length="medium" />
            </p>
        );
    }
```

## Styling Components in SCSS

### Selecting BEM

We can take advantage of [SCSS amperstand operator](https://css-tricks.com/the-sass-ampersand/#modifying-the-ampersand) to reduce the repetitiveness of selecting BEM classes:

{% code title="component/ProductCard/ProductCard.style.scss (excerpt, annotated)" %}

```css
.ProductCard {
    // style the block
    padding-left: 0;
    min-width: 0;

    &::before {
        content: none;
    }

    // & will get replaced with the parent selector, .ProductCard.
    // so this selects .ProductCard-Content (the Content element
    // of the ProductCart block
    &-Content {
        padding: 1rem;
        display: flex;
        flex-wrap: wrap;
        padding-top: 23px;
    }

    &-Brand {
        font-weight: 300;
        opacity: .5;
    }

    &-Figure {
        flex-grow: 1;
    }
    
    &-Name {
        width: 100%;
        font-size: .9rem;

        // this selector will compile to .ProductCard-Name_isLoaded
        &_isLoaded {
            text-overflow: ellipsis;
        }
    }
}
```

{% endcode %}

### Breakpoints

ScandiPWA defines certain breakpoints that enable you to write viewport width-specific styles. [These can be found in the global style directory](/structure/building-blocks-summary/global-styles#breakpoints). To select a specific device, simply use the `@include` directive:

```css
// ...
    &-Brand {
        font-weight: 300;
        opacity: .5;

        // will only affect mobile devices
        @include mobile {
            line-height: 1;
            font-size: 12px;
        }
    }
```

### CSS Variables

CSS variables are useful when:

* You want to reuse the same value multiple times
* You want to be able to override a value based on the context
* You want to make it more clear what a value represents by naming it

CSS variables are always defined in `:root`. That way, re-defining them anywhere else is an easy way to override them. Example:

{% code title="component/CartItem/CartItem.style.scss (simplified & annotated)" %}

```css
// we define variables in :root
:root {
    --cart-item-background: #fff;
    --cart-item-actions-color: #000;
}

.CartItem {
    &:hover  {
        // we can re-define them to override values
        --cart-item-actions-color: #222
    }
    
    &-Wrapper {
        background: var(--cart-item-background);
    }

    &-Delete {
        height: 35px;
        color: var(--cart-item-actions-color);
    }
}
```

{% endcode %}

{% hint style="info" %}
ScandiPWA uses an auto-prefixer. When compiling, vendor-specific versions of rules are added to make sure they work on most browsers.
{% endhint %}

## Mixes

Sometimes, you may want to allow other components to add additional style rules to a component. For example, the Image component needs to define some styles, but can't predict ahead of time the exact styling features that will be needed for Images in parent components.

The solution is to allow other components to add their own styles to the Image component. The BEM methodology allows this by "mixing" 2 BEM classes together. For example, in the CategoryDetails component, in addition to the regular `Image` block, the `CategoryDetails-Picture` class will be added. Since the element will now have both of these classes, the parent component can additionally style the element with new rules.

{% code title="component/CategoryDetails/CategoryDetails.component.js" %}

```jsx
    renderCategoryImagePlaceholder() {
        return (
            <Image
              mix={ { block: 'CategoryDetails', elem: 'Picture' } }
              objectFit="cover"
              ratio="custom"
              isPlaceholder
            />
        );
    }
```

{% endcode %}

{% hint style="info" %}
Note: to allow a component's styles to be mixed, you need to pass the `mix` prop to an element in the Component – this won't happen automatically. For example, consider how the Image component passes on the `mix` prop:

{% code title="component/Image/Image.component.js (simplified excerpt)" %}

```jsx
    render() {
        const {
            mix
        } = this.props;


        return (
            <div
              block="Image"
              mix={ mix }
            >
                { this.renderImage() }
            </div>
        );
    }
```

{% endcode %}
{% endhint %}


# Routes

Routes are pages in your single-page application

ScandiPWA uses [`react-router`](https://reactrouter.com/web/guides/quick-start) and `react-router-dom` to handle routing. The Router itself is a ScandiPWA component, defined in `component/Router/Router.component.js`.

The components for the routes themselves are defined in the `route` directory. The structure of each route is the same as any other [component](/structure/building-blocks-summary/components), but instead of being used throughout the application, they are used only in the router.

## Special Cases

ScandiPWA allows you to create custom URL rewrites that don't match the standard routes. This is implemented in `route/UrlRewrites`.

If neither the standard routes nor the URL rewrites resolve to anything, the user is shown an error message stating that no page has been found. This is implemented in `route/NoMatchHandler`.

## Example - Standard Route

The `CartPage` is defined in `route/CartPage`.  It is added to the router:

```javascript
// component/Router/Router.component.js


// imported lazily for better performance
export const CartPage = lazy(() => import(/* webpackMode: "lazy", webpackChunkName: "cart" */ 'Route/CartPage'));
// [...]

/** @namespace Component/Router/Component */
export class Router extends PureComponent {
    // all standard routes are defined here, including the cart page
    [SWITCH_ITEMS_TYPE] = [
        {
            component: <Route
              path={ withStoreRegex('/cart') }
              exact
              render={ (props) => <CartPage { ...props } /> }
            />,
            // position is the "priority" of this route.
            // routes with lower position will be rendered first,
            // and if several routes match the same URL, the first one is shown
            position: 50
        },
        // [...]
    ];
    // [...]
}

export default Router;
```

Now, whenever the user visits `/cart`, the `CartPage` will be shown.

## Example - URL Rewrite

The ProductPage is defined in `route/ProductPage`. It is rendered when `UrlRewrites` resolves an URL to a `TYPE_PRODUCT` page:

```jsx
// route/UrlRewrites/UrlRewrites.component.js


    renderContent() {
        const { props, type, updateNoMatch } = this.props;

        switch (type) {
        case TYPE_PRODUCT:
            return <ProductPage { ...props } />;
        case TYPE_CMS_PAGE:
            return <CmsPage { ...props } />;
        case TYPE_CATEGORY:
            return <CategoryPage { ...props } />;
        case TYPE_NOTFOUND:
            updateNoMatch({ noMatch: true });
            return <NoMatch { ...props } />;
        default:
            return this.renderDefaultPage();
        }
    }
```


# Redux Stores

Redux stores are used to maintain global state

{% hint style="info" %}
The term "store" in this article refers to a JavaScript object we can use to keep track of state. Not to be confused with Magento stores.
{% endhint %}

ScandiPWA uses [Redux](https://redux.js.org/introduction/getting-started) to keep track of global state. If used correctly, Redux is predictable and easy to debug.

{% hint style="success" %}
If you need to keep a state that will be the same throughout the application, and possibly shared between multiple components, create a Redux store. Examples:

* Breadcrumbs are stored in a Redux store, because multiple pages need to be able to set them
* The Cart is stored in a Redux store, because both the cart overlay and the cart page need to use it, though they don't have a direct parent-child relationship. Also, it is guaranteed that you will need information about only 1 cart in the application.
  {% endhint %}

{% hint style="warning" %}
Avoid using a global Redux store if it is component-specific, and you need to be able to store a different value in each component. Use the component's state instead.
{% endhint %}

Following Redux practices, the ScandiPWA theme contains 1 Redux store. However, since the application needs to maintain different kinds of global state, the top-level Redux store actually tracks an object containing multiple "sub-stores". Each of these sub-stores, has a dedicated subdirectory in `store` where it is defined.

The use of Redux stores is not ScandiPWA-specific, so it is best to learn about it from the [oficial redux documentation](https://redux.js.org/introduction/getting-started). However, we will go though an example of how it is used in ScandiPWA to help you understand how it interacts with the application.

## Example: Breadcrumbs

At the top of many pages in the ScandiPWA theme, you will see breadcrumbs. These are path-like indicators that help the user understand where in the app they currently are, and improve navigation:

![](/files/-MO0KsAC-dBKf5EuGGFU)

Since we would never need to keep track of multiple different breacrumb paths at once, they are implemented in a global redux store. Once a component receives enough data to know what the breadcrumbs should be (such as a list of parent categories as above), it dispatches an update to the breadcrumbs. This state can now be read by the component responsible for rendering breadcrumbs.

### 1. The `.reducer` File: Defining the State

See [store/Breadcrumbs/Breadcrumbs.reducer.js](https://github.com/scandipwa/scandipwa/blob/master/packages/scandipwa/src/store/Breadcrumbs/Breadcrumbs.reducer.js).

The first step in creating a redux store is defining what it's initial state should be. We need to keep track of two things - the breadcrumbs themselves and a boolean indicating wether they should be visible on the current page.

```javascript
/** @namespace Store/Breadcrumbs/Reducer/getInitialState */
export const getInitialState = () => ({
    breadcrumbs: [],
    areBreadcrumbsVisible: true
});
```

Now, we need to describe how the state should update in response to certain actions. It might seem unintuitive at first, but Redux state cannot be updated directly. Instead, you are allowed to define [reducers](https://redux.js.org/understanding/thinking-in-redux/glossary#reducer) - functions that describe how the state should transition when an [action](https://redux.js.org/understanding/thinking-in-redux/glossary#action) is [dispatched](https://redux.js.org/understanding/thinking-in-redux/glossary#dispatching-function).

We want to handle two types of actions - one that updates the breadcrumbs, and one that updates their visibility:

```javascript
// action types are actually defined in the .action file
export const UPDATE_BREADCRUMBS = 'UPDATE_BREADCRUMBS';
export const TOGGLE_BREADCRUMBS = 'TOGGLE_BREADCRUMBS';
```

All actions are simple JavaScript objects that carry information which the reducer can interpret to update the state. The `UPDATE_BREADCRUMBS` action would look like this:

```javascript
{
    // all actions have a `type` field that indicates
    // what kind of action it is. it must be unique
    // among action types
    type: UPDATE_BREADCRUMBS,
    // the UPDATE_BREADCRUMBS type action also has a 
    // breadcrumbs field that carries information
    // about what the new breadcrumbs should be
    breadcrumbs: [...] // some array
}
```

The `TOGGLE_BREADCRUMBS` would be similar, but carry a different type of data, a boolean:

```javascript
{
    type: TOGGLE_BREADCRUMBS,
    // the UPDATE_BREADCRUMBS type action also has a 
    // breadcrumbs field that carries information
    // about what the new breadcrumbs should be
    areBreadcrumbsVisible: true // or false
}
```

Note that, by themselves, actions do not do anything - they are just objects with some fields. However, when an action is dispatched ("sent" to Redux, we'll get to that later), Redux passes it on to all reducers (functions we define). Each reducer can look at the action and update its state by returning a new value.

```javascript
/** @namespace Store/Breadcrumbs/Reducer */
export const BreadcrumbsReducer = (
    state = getInitialState(), // previous state, or the initial state if none
    action // we get the action that was dispatched
) => {
    // we are only interested in certain types of actions
    switch (action.type) {
    
    // if this is an UPDATE_BREADCRUMBS action
    case UPDATE_BREADCRUMBS:
        // we know that it will have some data in action.breadcrumbs
        const { breadcrumbs } = action;

        // we update the state
        return {
            ...state, // to keep the same value
            breadcrumbs // except with a new breadcrumbs value
        };

    // similarly we want to update the state for TOGGLE_BREADCRUMBS actions
    case TOGGLE_BREADCRUMBS:
        const { areBreadcrumbsVisible } = action;

        return {
            ...state,
            areBreadcrumbsVisible
        };

    default:
        // it is possible the action was not related to breadcrumbs.
        // then we can just return the original state unchanged.
        return state;
    }
};
```

Now that the reducer is defined, we need to include it in our single global Redux state.

{% code title="src/app/store/index.js (simplified & annotated)" %}

```javascript
// copyright

import {
    combineReducers,
    createStore
} from 'redux';

import BreadcrumbsReducer from 'Store/Breadcrumbs/Breadcrumbs.reducer';
// [...] import the other reducers

/** @namespace Store/Index/getReducers */
export const getStaticReducers = () => ({
    BreadcrumbsReducer,
    // [...] include the other reducers
});

// a bunch of Redux API calls essentially creating a store from the above
```

{% endcode %}

### 2. The `.action` File: Defining Possible Actions

The reducer we created can respond to certain actions described above, but creating those action object manually would get repetitive and error-prone. Hence, we create functions that can create these action objects for us:

{% code title="store/Breadcrumbs/Breadcrumbs.action.js (simplified)" %}

```javascript
export const UPDATE_BREADCRUMBS = 'UPDATE_BREADCRUMBS';
export const TOGGLE_BREADCRUMBS = 'TOGGLE_BREADCRUMBS';

export const updateBreadcrumbs = (breadcrumbs) => ({
    type: UPDATE_BREADCRUMBS,
    breadcrumbs
});

export const toggleBreadcrumbs = (areBreadcrumbsVisible) => ({
    type: TOGGLE_BREADCRUMBS,
    areBreadcrumbsVisible
});

```

{% endcode %}

{% hint style="warning" %}
Redux strongly discourages creating side effects in the reducer, or action creators. Avoid making requests, mutating non-Redux state, or other changes in these functions. This will make them less predictable and harder to debug.&#x20;
{% endhint %}

### 3. The `.dispatcher` File: Dispatching Helpers

It can be convenient to have a file that defines helpers for dispatching actions. That's what the `.dispatcher` file is for:

{% code title="store/Breadcrumbs/Breadcrumbs.dispatcher.js (simplified, annotated)" %}

```javascript
import { toggleBreadcrumbs, updateBreadcrumbs }
  from 'Store/Breadcrumbs/Breadcrumbs.action';

/** @namespace Store/Breadcrumbs/Dispatcher */
export class BreadcrumbsDispatcher {
    // utility method for updating breadcrumbs
    // given a category,
    // and a dispatch function (from Redux)
    updateWithCategory(category, dispatch) {
        const breadcrumbs = this._getCategoryBreadcrumbs(category);
        dispatch(toggleBreadcrumbs(true));
        dispatch(updateBreadcrumbs(breadcrumbs));
    }
    
    _getCategoryBreadcrumbs(category) {...}

    updateWithProduct(product, dispatch) {...}

    updateWithCmsPage(cmsPage, dispatch) {...}
}

export default new BreadcrumbsDispatcher();
```

{% endcode %}

Unlike the reducer or action creators, you are free to have side effects in the dispatcher. For example, in [`store/Cart/Cart.dispatcher.js`](https://github.com/scandipwa/scandipwa/blob/master/packages/scandipwa/src/store/Cart/Cart.dispatcher.js), `addProductToCart` makes a GraphQl mutation request before updating the store by dispatching a cart data update.

### 4. Usage in Components

Any component's container can read and dispatch to the Redux state by using the [`connect` higher-order component](https://react-redux.js.org/api/connect).

#### Reading the state: Example

```jsx
import { connect } from 'react-redux';

import Breadcrumbs from './Breadcrumbs.component';

// given the global state, need to return an object
// containing the values of the state that we need
// these will be passed as props to Breadcrumbs
/** @namespace Component/Breadcrumbs/Container/mapStateToProps */
export const mapStateToProps = (state) => ({
    breadcrumbs: state.BreadcrumbsReducer.breadcrumbs,
    areBreadcrumbsVisible: state.BreadcrumbsReducer.areBreadcrumbsVisible
});

// we specify mapDispatchToProps even though we don't need it
// so that ScandiPWA plugins can use it if necessary
/** @namespace Component/Breadcrumbs/Container/mapDispatchToProps */
export const mapDispatchToProps = () => ({});

// Breadcrumbs will get breadcrumbs and areBreadcrumbsVisible as props
export default connect(mapStateToProps, mapDispatchToProps)(Breadcrumbs);
```

#### Dispatching to the state: Example

{% code title="route/CategoryPage/CategoryPage.container.js (simplified, annotated)" %}

```javascript
// we use a lazy import for better performance
export const BreadcrumbsDispatcher = import(
    /* webpackMode: "lazy", webpackChunkName: "dispatchers" */
    'Store/Breadcrumbs/Breadcrumbs.dispatcher'
    );

/** @namespace Route/CategoryPage/Container/mapStateToProps */
export const mapStateToProps = (state) => {...};

/** @namespace Route/CategoryPage/Container/mapDispatchToProps */
export const mapDispatchToProps = (dispatch) => ({
    updateBreadcrumbs: (breadcrumbs) => ((Object.keys(breadcrumbs).length)
            ? BreadcrumbsDispatcher.then( // promise to load BreadcrumbsDispatcher
                ({ default: dispatcher }) =>
                  dispatcher.updateWithCategory(breadcrumbs, dispatch)
            )
            : BreadcrumbsDispatcher.then(
                ({ default: dispatcher }) =>
                  dispatcher.update([], dispatch)
            )
    ),
    // [...]
});

/** @namespace Route/CategoryPage/Container */
export class CategoryPageContainer extends PureComponent {
    // updateBreadcrumbs will be passed as props when
    // react-redux wraps the component
    static propTypes = {
        updateBreadcrumbs: PropTypes.func.isRequired,
        // [...]
    };

    componentDidMount() {
        this.updateBreadcrumbs();
    }

    componentDidUpdate(prevProps) {
        this.updateBreadcrumbs();
    }

    updateBreadcrumbs(isUnmatchedCategory = false) {
        // we can simply get the function from props and call it
        const { updateBreadcrumbs, category } = this.props;
        const breadcrumbs = isUnmatchedCategory ? {} : category;
        updateBreadcrumbs(breadcrumbs);
    }
    
    render() {...}
}

export default connect(mapStateToProps, mapDispatchToProps)
    (CategoryPageContainer);
```

{% endcode %}


# GraphQL Queries

GraphQL queries are generated dynamically using javascript

Magento exposes a [GraphQL](https://graphql.org/) API, which ScandiPWA builds upon and uses to fetch and mutate data. GraphQL is more flexible than REST, and offers a type checking system.

You might want to [read the GraphQL documentation](https://graphql.org/learn/) to get a full understanding of the language, but we do provide a quick introduction below.

## Introduction to GraphQL

GraphQL queries look like JSON objects without quotes or values, and describe which fields you want to fetch from the server:

```graphql
{
  categoryList {
    name
    children {
      name
      url
      product_count
    }
  }
}
```

In this case, we want to query the `categoryList` field, and fetch its name and children fields. For the children, we want to fetch the name, URL, and product count. It is not clear from the query, but if you were to look at the GraphQl schema, you would know that `children` is actually an array of such objects.

If we make the above request to the GraphQL endpoint on a Magento instance (it's usually `/graphql`), we would get the following JSON response:

```javascript
{
  "data": {
    "categoryList": [
      {
        "name": "Default Category",
        "children": [
          {
            "name": "Audio, Video & Photo Equipment",
            "url": "/audio-video-photo-equipment.html",
            "product_count": 0
          },
          {
            "name": "Home Security & Automation",
            "url": "/home-security-automation.html",
            "product_count": 0
          },
          {
            "name": "Computers, Peripherals & Accessories",
            "url": "/computers-peripherals-accessories.html",
            "product_count": 1
          },
          {
            "name": "Office",
            "url": "/office.html",
            "product_count": 2
          },
          {
            "name": "Telecom & Navigation",
            "url": "/telecom-navigation.html",
            "product_count": 0
          },
          // [...]
        ]
      }
    ]
  }
}
```

{% hint style="info" %}
The `categoryList` query has other fields that we could fetch (such as `id`, `description`), but GraphQL only returns the ones we asked for.
{% endhint %}

## API

### Generating GraphQL Queries

Commonly, when building an application, GraphQL queries are specified in the code as strings, much like the query above. While this method does have its advantages, it is hard to extend - what happens when you want to override a theme and need to fetch one more field from the same query? If queries were hardcoded as strings, you would have to copy and edit the string to create a new query in your theme, which would quickly get hard to maintain. And what about plugins? There would be no easy way to write a plugin that asks for an additional field in some query.

The solution ScandiPWA proposes is to generate queries dynamically. Then, adapting the query to your needs is as easy as using the [Override Mechanism](/developing-with-scandi/override-mechanism/extending-javascript) on the query's class.&#x20;

ScandiPWA implements its own library to enable you to generate GraphQL queries and mutations. This is defined in `util/Query`.

#### Field

Represents a [GraphQL field](https://graphql.org/learn/queries/#fields)

| Method                                                                                                                                                                 | Description                                                                                                                                  |                                                                                                                                                                                                                         |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <p><code>addField(</code></p><p>  <code>field: Field                                                                                                                   | string</code></p><p><code>) -> this</code></p>                                                                                               | Adds the specified child field to this field as a subfield. The child field must be either an instance of Field, or a string. If a string is specified, this is equivalent to calling `addField(new Field(string))`     |
| <p><code>addFieldList(</code></p><p>  <code>fields:(Field                                                                                                              | string)\[]</code></p><p><code>) -> this</code></p>                                                                                           | Adds the specified list of child fields to this field as subfields. Each item of `fields` must be either an instance of Field, or a string. If a string is specified, this is equivalent to passing `new Field(string)` |
| <p><code>addArgument(</code></p><p>  <code>name: string,</code></p><p>  <code>type: string,</code></p><p>  <code>value: string</code></p><p><code>) -> this</code></p> | Adds an argument called `name` with a value of `value` of type `type` to this field. All three parameters must be strings, and are required. |                                                                                                                                                                                                                         |
| <p><code>setAlias(</code></p><p>  <code>alias: string</code></p><p><code>) -> this</code></p>                                                                          | Aliases the field to the specified alias.                                                                                                    |                                                                                                                                                                                                                         |

#### Fragment

Represents a [GraphQL fragment](https://graphql.org/learn/queries/#fragments). Extends Field, and hence has the same API.

#### Example

```javascript
import { Field, prepareFieldString } from 'Util/Query';

const childrenField = new Field('children')
    .addFieldList([
        'name',
        'url',
        'productCount'
    ]);

const query = new Field('categoryList')
    .addArgument('filter', 'CategoryFilterInput', {})
    .addField('name')
    .addField(childrenField)
    .setAlias('categories');

console.log(prepareFieldString(query));
```

Resulting Query (argument valus are sent separately):

```graphql
categories:categoryList(filter:$filter_1){name,children{name,url,productCount}}
```

### Making Queries

Functions that make requests are defined in `util/Request`. These implement a smart caching mechanism to improve performance and reduce load.

| Function                                                  | Description                                                   |                                                                                                                                                                     |
| --------------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <p><code>fetchQuery(</code></p><p>  <code>query: Field    | Field\[]</code></p><p><code>) -> Promise\<Request></code></p> | Fetches a query or array of queries, and returns a Promise that resolves when the query successfully completes. If the query fails, so does the Promise.            |
| <p><code>fetchMutation(</code></p><p>  <code>query: Field | Field\[]</code></p><p><code>) -> Promise\<Request></code></p> | Fetches a mutation or array of mutations, and returns a Promise that resolves when the mutation successfully completes. If the mutation fails, so does the Promise. |

### QueryDispatcher

`QueryDispatcher` is a base class for redux dispatchers that can simplify making queries. Dispatchers that need to make queries typically need to make the request, and then handle the resulting data or any errors. `QueryDispatcher` automates this this by implementing a `handleData` function that performs this logic.

A subclass extending `QueryDispatcher` will need to define 3 functions. Once these 3 functions are defined, `handleData` will work automatically as expected.

* `prepareRequest(options, dispatch)`, a function that returns the query that the dispatcher wants to make
* `onSuccess(data, dispatch)`, a function that is called with the response data when the query completes successfully
* `onError(error, dispatch)`, a function that is called on request error

Note that all 3 functions get access to Redux's `dispatch` function in case they need to use it (e.g. to show a notification).

## GraphQL in ScandiPWA

To keep the codebase organized, we don't want the components or redux dispatchers to be responsible for generating queries. Instead, we keep all query-generating code in `query`.

By convention, `query` contains 1 JavaScript file for each group of related queries. For example, the `Cart.query.js` file contains queries relating to querying or mutating the customer's cart. All of these files define classes with one or more functions to generate some query, as well as "private" helper methods (optionally). These classes are exported as singleton instances intended to be used by the rest of the app. For an example, consider the [`Cart.query.js`](https://github.com/scandipwa/scandipwa/blob/master/packages/scandipwa/src/query/Cart.query.js) file:

{% code title="query/Cart.query.js (simplified, annotated)" %}

```javascript
import { isSignedIn } from 'Util/Auth';
import { Field } from 'Util/Query';

/** @namespace Query/Cart */
export class CartQuery {
    // creates a query for getting cart data
    // caller method expected to provide certain arguments
    getCartQuery(quoteId) {
        const query = new Field('getCartForCustomer')
            .addFieldList(this._getCartTotalsFields())
            .setAlias('cartData');

        // since queries are generated dynamically, we can add different
        // arguments based on certain conditions
        if (!isSignedIn()) {
            query.addArgument('guestCartId', 'String', quoteId);
        }

        return query;
    }

    getSaveCartItemMutation(product, quoteId) {
        const mutation = new Field('saveCartItem')
            .addArgument('cartItem', 'CartItemInput!', product)
            .addFieldList(this._getSaveCartItemFields(quoteId));

        if (!isSignedIn()) {
            mutation.addArgument('guestCartId', 'String', quoteId);
        }

        return mutation;
    }

    getRemoveCartItemMutation(item_id, quoteId) {...}

    getApplyCouponMutation(couponCode, quoteId) {...}

    getRemoveCouponMutation(quoteId) {...}

    // [...] helper methods not intended for public use
}

export default new CartQuery();
```

{% endcode %}

When a query generation file is defined, it can be used to make requests in dispatchers as well as component's containers.


# Global Styles

While each component may have its own styles, some styles are globally defined

ScandiPWA defines certain global styles. These are defined in SCSS in the `style` directory, which contains 3 subdirectories, as well as a file that imports all the styles from them.

## Directories in `style`

### `abstract`

The `abstract` subdirectory includes SCSS definitions that do not directly affect any element. Instead, they define utilities such as media selectors (`_media.scss`), SCSS variable declarations (`_variables.scss`) and some generic styles that could be used to style an element (`_loader.scss`, `_button.scss`).

### `base`

This directory defines the base styles for various HTML elements, such as tables, links, buttons and lists. In `_breakpoints.scss`, it defines classes that can be added to elements to easily hide them on some devices (mobile/tablet/desktop)

### `cms`

This directory contains styles for defining the look of various content inserted through a content management system (typically Magento CMS). Currently, it styles various promotion blocks, as well as sliders.

## Breakpoints

| Selector           | Visible on Mobile | Visible on Tablet | Visible on Desktop |
| ------------------ | ----------------- | ----------------- | ------------------ |
| `desktop`          |                   |                   | ✔️                 |
| `before-desktop`   | ✔️                | ✔️                |                    |
| `tablet`           |                   | ✔️                |                    |
| `tablet-landscape` |                   | landscape only    |                    |
| `after-mobile`     |                   |                   | ✔️                 |
| `mobile`           | ✔️                |                   |                    |

To use a breakpoint, use `include`:

```css
@include mobile {
    // your styles
}
```


# The Util Directory

The util directory in ScandiPWA contains various utility functions that don't belong in the other categories

Some Util directories, such as Address, Cart, Currency, Menu, Price, Product, facilitate working with certain kinds of data. Others define commonly-needed functionality:

| `util` directory    | Purpose                                                                                                                                                               |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Auth                | Responsible for user authentication                                                                                                                                   |
| BrowserDatabase     | Convenience methods for accessing the browser's local storage with a simpler API                                                                                      |
| CSS                 | CSS manipulation. E.g. an utility for setting a CSS variable. This is useful if you want to be able to dynamically edit theme colors from the admin panel.            |
| Extensions          | Implements a part of the plugin mechanism                                                                                                                             |
| FormPortalCollector | Works with form data                                                                                                                                                  |
| History             | Exports a history object using the [`history`](https://www.npmjs.com/package/history) library.                                                                        |
| Media               | Utility for working with media URLs                                                                                                                                   |
| Mobile              | Function that helps determine the browser device, and wether it is a mobile device.                                                                                   |
| Polyfill            | Defines [polyfills](https://en.wikipedia.org/wiki/Polyfill_\(programming\)). Currently includes the [smoothscroll polyfill](https://iamdustan.github.io/smoothscroll) |
| Promise             | Implements a cancelable promise                                                                                                                                       |
| Query               | Defines tools to work with GraphQL queries                                                                                                                            |
| Request             | Defines tools to work with Requests                                                                                                                                   |
| Url                 | Tools to work with URLs and parse them.                                                                                                                               |

Finally, there is the `Braintree` directory, which can handle interaction with the Braintree payment method.

{% hint style="success" %}
When [overriding the theme](/developing-with-scandi/override-mechanism) or creating plugins, you are allowed to [override Util files](/developing-with-scandi/override-mechanism/extending-javascript) to add functionality, or to create your own Util files.
{% endhint %}


# Type Checking

ScandiPWA uses PropTypes to help verify that the expected props are passed, and catch bugs

[PropTypes](https://www.npmjs.com/package/prop-types) is a library for React that verifies at runtime if props have the correct type, and if required props are passed.&#x20;

{% hint style="warning" %}
PropTypes are only used in development builds, which means that production builds will be more efficient - but it also means that you should not rely on PropTypes being present in the production build!
{% endhint %}

PropTypes are declared as a static field:

{% code title="component/CategoryPaginationLink/CategoryPaginationLink.component.js (annotated excerpt)" %}

```javascript
import { ChildrenType } from 'Type/Common';

export class CategoryPaginationLink extends PureComponent {
    static propTypes = {
        // component children are treated as props in React
        // by leaving out .isRequired, we make them optional
        children: ChildrenType,
        // we expect the getPage prop to be a function
        // it is required, so this will emit a warning if
        // getPage is not provided
        getPage: PropTypes.func.isRequired,
        // isCurrent expects a boolean
        isCurrent: PropTypes.bool.isRequired,
        // expects a string
        url_path: PropTypes.string.isRequired,
        // expects a number
        pageNumber: PropTypes.number.isRequired
    };
    
    static defaultProps = {
        // we must provide a default value for every prop that
        // is optionalex
        children: []
    };
```

{% endcode %}

## Advanced Types

Sometimes you want to specify that a prop expects something more complex than a number or a string. You can check the [official documentation](https://www.npmjs.com/package/prop-types#usage) to learn how to use:

* `instanceOf` to expect an instance of a class
* `oneOf` to expect an enum-like value that can have one of the specified values
* `oneOfType` to indicate that this prop may have one of the specified types
* `arrayOf` or `objectOf` to specify that this prop is an array or object where each value has the specified type
* `shape` or `exact` to specify that the prop should be an object, as well as what keys and values you expect it to have

For example, you could specify that a prop expecting a payment method should be given an object with two string properties:

```javascript
PropTypes.shape({
    code: PropTypes.string,
    title: PropTypes.string
});
```

However, it is likely that this "complex" type would be needed in multiple places in the application. Copy-pasting the same PropType definition would lead to code duplication, and a hard-to-maintain codebase. Instead, we prefer defining these types in the `type` directory and exporting them for re-use:

{% code title="type/Checkout.js (excerpt)" %}

```javascript
import PropTypes from 'prop-types';

export const paymentMethodType = PropTypes.shape({
    code: PropTypes.string,
    title: PropTypes.string
});
```

{% endcode %}

Now, we can use this type definition anywhere we want:

{% code title="component/CheckoutPayment/CheckoutPayment.component.js (excerpt)" %}

```javascript
import { paymentMethodType } from 'Type/Checkout';

/** @namespace Component/CheckoutPayment/Component */
export class CheckoutPayment extends PureComponent {
    static propTypes = {
        method: paymentMethodType.isRequired,
        onClick: PropTypes.func.isRequired,
        isSelected: PropTypes.bool
    };
```

{% endcode %}


# Application assets

Add static assets to your app, such as fonts and favicons

Right next to the `src` directory is the `public` directory, which is meant for static assets. In the default Scandi theme, it includes an `index.html` file. You can also add other files you would like to be statically served.


# Code Style

Following our coding style helps maintain a consistent and well-organized codebase

When coding in JavaScript, it is important to keep in mind functional programming practices, as well as Scandi-specific conventions that ensure your code will work well with the rest of the ecosystem.

{% content-ref url="/pages/-MO\_8YZXZtQa8oAHbxMi" %}
[JavaScript Code Style](/structure/code-style/javascript-code-style)
{% endcontent-ref %}

SCSS code style helps keep your code easy to navigate and adapt.

{% content-ref url="/pages/-MO\_rePLbRL330D37\_an" %}
[SCSS Code Style](/structure/code-style/scss-code-style)
{% endcontent-ref %}


# 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](broken://pages/-MOVDCW03Ud-lKz1G5Il) 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.

{% hint style="danger" %}
Avoid writing long functions such as this:

{% code title="component/MyAccountOverlay/MyAccountOverlay.component.js (renderCreateAccount function)" %}

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

{% endcode %}

Issues:

* Code is harder to understand and navigate
* Re-ordering or re-using sub-components requires a lot of changes
  {% endhint %}

{% hint style="success" %}
Instead, try breaking your code into smaller functions:

```jsx
    // 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
  {% endhint %}

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

[Destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) enables you to "unpack" certain values from an object such as the state or props.

```javascript
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

{% hint style="danger" %}
It can be tempting to pass a hard-coded literal value to a function:

```javascript
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.
{% endhint %}

{% hint style="success" %}
Instead, consider creating a constant to describe the meaning of the value:

{% code title="component/Slider/Slider.component.js (simplified excerpt)" %}

```javascript
export const ANIMATION_DURATION = 300;

CSS.setVariable(
    this.draggableRef,
    'animation-speed',
    `${ Math.abs(distance * ANIMATION_DURATION) }ms`
);
```

{% endcode %}

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
  {% endhint %}

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

{% hint style="danger" %}
Avoid `let` and reassigments:

```javascript
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
  {% endhint %}

{% hint style="success" %}
Instead prefer:

```javascript
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&#x20;
  {% endhint %}

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

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`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce).

{% hint style="danger" %}
Avoid using a loop to combine the array's elements:

```javascript
const items = [2, 4, 24, 42];
let sum = 0;
for (let i = 0; i < items.length; i++) {
    sum += items[i];
}
```

{% endhint %}

{% hint style="success" %}
Instead prefer `reduce`:

```javascript
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 see this for the first time, it might seem counter-intuitive. Perhaps [MDN's docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) will help!
{% endhint %}

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

{% hint style="danger" %}
Avoid using a loop to transform an array:

```jsx
const items = [2, 4, 24, 42];
const newItems = []
for (let i = 0; i < items.length; i++) {
    newItems.push(`Item ID: ${ items[i] }`);
}
```

{% endhint %}

{% hint style="success" %}
Instead, use [`map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/map):

```jsx
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`
  {% endhint %}

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

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](https://reactjs.org/docs/state-and-lifecycle.html) and [Redux](https://redux.js.org/) handles state - instead of giving you access to modify the state directly, you are expected to provide new values for the state.

[MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) 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.

#### Creating a new array by transforming each item

[`map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/map): calls the function for each value and returns the array of results

{% hint style="info" %}
`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:

{% code title="component/ProductCustomizableOptions/ProductCustomizableOptions.component.js (excerpt)" %}

```jsx
        return options.map((option, key) => (
            <ProductCustomizableOption
              option={ option }
              key={ key }
            />
        ));
```

{% endcode %}
{% endhint %}

#### Reducing the array to 1 value

[`reduce`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce): combines all elements into 1 new value, according to the specified reducing function

[`some`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some): returns true if and only if the specified function returns true for at least 1 element in the array

[`every`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every): returns true if and only if the specified function returns true for all elements

#### Copying part of the array

[`filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter): returns a copy of the array with only those items that meet the specified condition

[`slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice): returns a portion of the array specified by indices

#### Finding an item in the array

[`find`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find): returns the first element that meets the specified condition

[`includes`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes): returns true if and only if the specified element is in the array

#### Reordering the array

[`sort`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort): sorts the array according to the specified ordering function

[`reverse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse): reverses the items of the array

{% hint style="warning" %}
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.
{% endhint %}

#### Adding an item to the array

Instead of mutating the array, you can create a new array with the additional value:

```javascript
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`:

```javascript
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

If a plugin or [theme override](/developing-with-scandi/override-mechanism) 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.

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

```javascript
/** @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

ScandiPWA has a specific file structure: the source directory [contains 7 sub-directories](/structure/building-blocks-summary), 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.

### One Class Per File

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


# SCSS Code Style

ScandiPWA follows a strict SCSS style guide for maintainability and consistency

ScandiPWA follows the BEM methodology to write styles. In addition, there are some guidelines to help you write more maintainable stylesheets.

We strongly recommend you use [Stylelint](broken://pages/-MOVDCW03Ud-lKz1G5Il) to check your code style. This article was written to help you understand the code style rules we enforce and write better code.

## Avoid Non-BEM Selectors

{% hint style="danger" %}
Avoid using selectors that aren't Block-Element-Modifier classes:

{% code title="component/CheckoutOrderSummary/CheckoutOrderSummary.style.scss (fragment)" %}

```css
.CheckoutOrderSummary {
    &-CartItemDescription {
        margin-top: 5px;

        p {
            font-size: 1.1rem;
            line-height: 1.5;
        }
    }
}
```

{% endcode %}

Potential issues:

* As code becomes more complex, it can become unclear what role the `p` plays in the app. A well-named BEM class would describe its purpose better
* Unnecessary coupling: JavaScript code is now forced to use a `<p>` element. If you want to rewrite the component to use a `<div>` instead, you would have to update the styles.
* Unnecessary [specificity](https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity): Having an additional selector element will increase the specificity and make the styles harder to [override in `.override.style.scss` files](/developing-with-scandi/override-mechanism/extending-styles#partially-overriding-a-components-styles).
  {% endhint %}

{% hint style="success" %}
Instead, it is recommended to assign a new BEM class to the element you want to style. Then, you can select it properly:

```css
.CheckoutOrderSummary {
    &-CartItemDescription {
        margin-top: 5px;
    }
    
    &-CartItemDescriptionContent {
        font-size: 1.1rem;
        line-height: 1.5; 
    }
}
```

{% endhint %}

## Minimize Nesting

In some cases, you might need to style one Block differently depending on whether it is a child of another block.

{% hint style="danger" %}
Avoid nesting selectors - don't select a block nested inside another block:

```css
.Button {
    margin: 5px;
}

// this is discouraged
.ContactForm .Button {
    margin: 10px;
}
```

This is discouraged for several reasons:

* `Button` styles now contain selectors from another component. This causes coupling, which will result in issues if the `.ContactForm` block is renamed
* The selector has higher specificity, making it harder to override.
  {% endhint %}

{% hint style="success" %}
Instead, consider using a variable to customize your styling:

{% code title="Button.style.scss" %}

```css
:root {
    --button-margin: 5px;
}

.Button {
    // by default, a value of 5px will be inherited from :root
    margin: var(--button-margin);
}
```

{% endcode %}

{% code title="ContactForm.style.scss" %}

```css
.ContactForm {
    // we can set the variable lower in the hierarchy to override it
    --button-margin: 10px;
}
```

{% endcode %}

Now, each component is responsible for its own styles only, and the specificity is still low.
{% endhint %}

{% hint style="info" %}
In this case, another possible solution would be to use a Modifier:

```css
.Button {
    margin: 5px;
    
    &_margin_large {
        margin: 10px;
    }
}
```

Now, the button could take a prop to specify the margin size and add it as a modifier to the Button Block. Then, the ContactForm would have to pass a value to this prop indicating that the button should have a larger margin.
{% endhint %}

## Prefer Mobile-First Styling

Mobile-first styling means that you are encouraged to start with mobile styles. When mobile styles are complete, you can use the `@include desktop` directive and other [breakpoints](/structure/building-blocks-summary/global-styles#breakpoints) to adjust the styling for larger viewports.

## Avoid Magic Numbers

You should avoid hard-coding numbers when their reasoning is not obvious. Make values re-usable and understandable by using CSS variables.

{% hint style="danger" %}
Avoid hard-coded values:

```css
.NotificationList {
    position: fixed;
    top: 110px;
}
```

Issues:

* Unclear where the value 110 is coming from
* The layout can break if some values are changed
  {% endhint %}

{% hint style="success" %}
Instead, use variables, and combine them with `calc` if necessary:

```css
.NotificationList {
    position: fixed;
    top: calc(var(--header-height) + var(--breadcrumbs-height) + 20px);
}
```

Advantages:

* Intentions are more clear
* Variable values can be safely changed to adjust the layout
* Each value is re-usable.
  {% endhint %}

## Use Round Numbers

Unless you need to produce a pixel-perfect design, we recommend using values rounded to the nearest 5px, avoid unnecessary decimal digits, and consider how much precision browsers can actually display.


# Customizing Your Theme

A walk-through of how you can create your own Scandi-based theme

In this tutorial, we will customize a Scandi theme in various ways, showing you specific examples of how common changes can be made. You can easily follow along with your own Scandi theme.

## Before You Start

You will need a Scandi app to customize, which you can easily get in a few minutes using `create-scandipwa-app` (CSA). Get started [here](https://docs.create-scandipwa-app.com/getting-started/getting-started)!

You can keep the `npm start` process running throughout this tutorial, so the changes you make will be automatically reflected in the browser.

There is no need for a Magento app, because CSA is clever and can forward all requests to a remote Magento instance.


# Styling

Give your theme a fresh coat of paint!


# Customizing the Global Styles

Define the overall look of your application

In Scandi, the `style` directory defines the overall look of your application - the theme colors, fonts, and some HTML element styling, along with CMS content styles. Often, when customizing a theme, these styles are the first thing to be changed.

Let's change the theme colors of the application. To override a style file, you will need to copy it from the base theme (which you can find in `node_modules/@scandipwa/scandipwa`) to your `src` directory, under the same path. In this case, the file we want to override is `node_modules/@scandipwa/scandipwa/src/style/abstract/_variables.scss`, because this is where the theme color variables are defined. Copy this file to `src/style/abstract/_variables.scss`. Now we are ready to customize the color variable declarations:

{% code title="src/style/abstract/\_variables.scss" %}

```css
$white: #FFF;
$black: #0D2E41;
$default-primary-base-color: #44AD9F;
$default-primary-dark-color: #00798A;
$default-primary-light-color: #87DEC7;
$default-secondary-base-color: #F8AF2C;
$default-secondary-dark-color: #FA6135;
$default-secondary-light-color: #FFE357;
```

{% endcode %}

However, you will notice that changes are not yet applied. This is because we also need to override the files in the import chain of the file. Inspecting the codebase, we see that `_variables.scss` is imported in  `_abstract.scss`, which is in turn imported in `src/style/main.scss`. Therefore, we also need to copy over `_abstract.scss` and `main.scss` from the base theme.

Now, the global style changes should be applied. Indeed, if we check our app, we can see the new colors:

![](/files/-MbpYlVwui5Mp_48lqp3)

**Exercise: So far, we only changed the theme colors. Take a look around the `style` directory and see if there is anything else you'd like to change. Perhaps change the loader background style, or the spacing in lists?**


# Adding a New Font

Learn to add and use new assets in Scandi

You can add fonts in the `src` directory. When you reference them with `url` in your style sheets, they will be resolved and bundled with your application. Let's go through an example.

{% hint style="info" %}
You can find fonts  in [Font squirrel](https://www.fontsquirrel.com/), [dafont.com](https://www.dafont.com/), [Everything Fonts](https://everythingfonts.com/) or [Google Fonts](https://fonts.google.com/).

In our case, it is best to [convert your fonts](https://www.fontsquirrel.com/tools/webfont-generator) into the woff format, which is optimized for the web. See [this article for more details](https://developer.mozilla.org/en-US/docs/Learn/CSS/Styling_text/Web_fonts).
{% endhint %}

Suppose you have decided to use the [Staatliches and Source Sans Pro fonts](https://fonts.google.com/share?selection.family=Source%20Sans%20Pro:wght@600%7CStaatliches). Convert them into the Woff and Woff2 formats. Then, put them in the `src/style/fonts` directory. Any directory under `src` would work, but it makes sense to store them in their own directory along with styles. Verify that your fonts are where you expect:

{% code title="$ ls src/style/fonts/" %}

```
sourcesanspro-regular-webfont.woff  sourcesanspro-regular-webfont.woff2
staatliches-regular-webfont.woff    staatliches-regular-webfont.woff2
```

{% endcode %}

However, they are still not imported in our styles. To actually include them into the page, create a new file, named`src/style/base/_font.scss`, for example. First, we will need to declare the font faces:

{% code title="src/style/base/\_font.scss" %}

```css
@font-face {
    font-family: 'Staatliches';
    src: url(/style/fonts/staatliches-regular-webfont.woff2) format('woff2'),
    url(/style/fonts/staatliches-regular-webfont.woff) format('woff');
    font-weight: normal;
    font-style: normal;
}

@font-face {
    font-family: 'SourceSansPro';
    src: url(/style/fonts/sourcesanspro-regular-webfont.woff2) format('woff2'),
    url(/style/fonts/sourcesanspro-regular-webfont.woff) format('woff');
    font-weight: normal;
    font-style: normal;
}
```

{% endcode %}

For convenience, let's also define SCSS variables to refer to these fonts:

{% code title="src/style/base/\_font.scss" %}

```css
$font-staatliches: 'Staatliches', sans-serif;
$font-source-sans-pro: 'SourceSansPro', sans-serif;
```

{% endcode %}

Now, we have told the browser that these fonts exist and where to find them. However, they aren't actually being used anywhere. Let's use the Staatliches font for headings, and the Source Sans font for everything else:

{% code title="src/style/base/\_font.scss" %}

```css
body {
    font-family: $font-source-sans-pro;
}

h1, h2, h3, h4, h5, h6 {
    font-family: $font-staatliches;
}
```

{% endcode %}

One final change is needed - to actually include the `_font.scss` file as part of the styles. We can do so by importing it in `src/style/main.scss` (override the file if you don't already have it in your code). In the last lines of `main.scss`, add:

{% code title="src/style/main.scss" %}

```css
@import './base/font';
```

{% endcode %}

{% hint style="warning" %}
Note: it is important that this import occurs after the import of `base/_reset.scss`, since, in the base theme, this is where the body font is set.
{% endhint %}

We can now refresh the page to see that the fonts have been updated:

![Result: fonts updated!](/files/-McO3X5k06igr66mKLJD)

**Exercise: Try adding a new font and assigning it to some text! Be careful though - the more fonts you add, the less consistent the user interface looks. Besides, each additional font needs to be loaded into the browser.**


# Overriding a Components Styles

Give a fresh new look to any component – replace the default styles with your own.

In the first few chapters, we looked at how global styles can be overridden in Scandi. However, for better code organization, individual components have their own styles, too. In this chapter, you will learn to override a component's styles.

Suppose you want to completely re-style an existing component in Scandi. All you need to do is override its `.style.scss` file by creating a new file of the same path, in your theme directory. For example:

```
to override:
    node_modules/@scandipwa/scandipwa/
        src/component/ProductAttributes/ProductAttributes.style.scss
create:
        src/component/ProductAttributes/ProductAttributes.style.scss
```

You can also use the `scandipwa` CLI utility to override styles more easily. Just be sure to select `Override` when asked what to do with styles, and leave the other options un-selected.

```
scandipwa override component ProductAttributes

? Choose things to extend in ProductAttributes.component.js 
? What would you like to do with styles? Override
? Choose things to extend in ProductAttributes.container.js 

NOTE!

     The following files have been created:
     src/component/ProductAttributes/ProductAttributes.style.scss
```

Now, it's time to write some custom styling for the component:

{% code title="src/component/ProductAttributes/ProductAttributes.style.scss" %}

```css
:root {
    --product-information-background: var(--secondary-base-color);
}

.ProductAttributes {
    &-Wrapper {
        padding: 0;

        @include desktop {
            padding: 2rem;
        }
    }

    &-ExpandableContentButton {
        @include after-mobile {
            display: none;
        }
    }

    &-ExpandableContentContent {
        &_isContentExpanded {
            @include mobile {
                padding: 0 1.4rem;
            }
        }
    }

    &-Description,
    &-Attributes {
        width: 100%;
    }

    &-Attributes {
        display: grid;
        grid-template-columns: 1fr 1fr;
        grid-gap:  1em;
    }

    &-ValueLabel,
    &-AttributeLabel {
        text-overflow: ellipsis;
        font-family: var(--font-staatliches);
        font-size: 20px;
        line-height: 1;
    }

    &-ValueLabel {
        font-weight: 700;
        @include mobile {
            padding-left: .7rem;
            margin-bottom: 1.4rem;
        }
    }

    &-AttributeLabel {
        text-align: right;
        color: var(--secondary-dark-color)
    }
}
```

{% endcode %}

![Result: a brand new look for the attributes component!](/files/-MciaMuMVAuukdTkGarv)


# Extending a Component's Styles

Tailor the details to your needs – make small adjustments while keeping the original styles

In the last chapter, we learned about overriding a component's styles. We did this by replacing the original styles with our own. However, in some cases, you may want to keep the original styles, and only make a few modifications. To avoid duplicating all of the original code, you can extend the styles by creating a file that will be used *in addition* to the base stylesheet.

For example, suppose you want to add a shadow to the menu overlay. To do this, we will only need to add a few lines of SCSS code – which means we want to keep most of the existing styling. This is easy to do – we simply need to extend the styles for the `Menu` component.

First, create a new stylesheet file:

{% code title="src/component/Menu/Menu.override.style.scss" %}

```css
.Menu {
    &-SubCategoriesWrapper {
        opacity: 0.95;
        box-shadow: rgba(0, 24, 49, 0.8) 0 30px 80px;
    }

    &-Overlay {
        display: none;
    }
}
```

{% endcode %}

Now, to include this file in the application (in addition to the existing styles), override the `.component` and import the `.override.style`:

{% code title="src/component/Menu/Menu.component.js" %}

```jsx
import {
    Menu as SourceMenu
} from 'SourceComponent/Menu/Menu.component';

import './Menu.override.style';

export default SourceMenu;
```

{% endcode %}

Note that the `.component` override doesn't make any changes – it simply re-exports the original component and imports an additional stylesheet.

{% hint style="info" %}
Note: you can use the `scandipwa` CLI utility to quickly override components. In this case, we can easily create the files we need with the following command:

```
scandipwa override component Menu
? Choose things to extend in Menu.component.js Menu
? What would you like to do with styles? Extend
? Choose things to extend in Menu.config.js 
? Choose things to extend in Menu.container.js 

NOTE!

     The following files have been created:
     src/component/Menu/Menu.override.style.scss
     src/component/Menu/Menu.component.js
```

{% endhint %}

Now, we have added a shadow and a transparency effect to the menu overlay:

![](/files/-McTbuETf8eIs_5VcYve)


# Customizing JavaScript

Change the JavaScript logic in your theme!

Everyone's needs are different – and the Scandi theme can be customized to meet a wide range of requirements. Some changes are very commonly needed, and this section provides a set of recipes of how to make them. Feel free to consult these guides while developing your Scandi theme!


# Customizing the Footer Copyright

Learn to override the copyright text at the bottom of the page

In this tutorial, we will override the component responsible for rendering the copyright text, and change that text to a different value. This will give you an insight into how the override mechanism works with React components. After completing this tutorial, you will be able to override other components as well.

## Inspecting the Code

Before we can modify its code, we need to know which component is responsible for rendering the Footer. We can use the [React developer tools](/developing-with-scandi/developer-tools#browser) to find out. Open the `Components` tab, and identify the element you want to override. In this case, it appears to be a child of the `Footer` component.

![](/files/-MePPS8pJYpXhBW8R6f-)

Now that we have found out the name of the component, there is only one place we need to look – the `component` directory in `node_modules/@scandipwa/scandipwa/src/`. Indeed, we can find a component named Footer there:

{% code title="Original File in scandipwa/src/component/Footer/Footer.component.js" %}

```javascript
// [...]
export class Footer extends PureComponent {
    // [...]
    
    renderCopyrightContent() {
        const { copyright } = this.props;

        return (
            <ContentWrapper
              mix={ { block: 'Footer', elem: 'CopyrightContentWrapper' } }
              wrapperMix={ { block: 'Footer', elem: 'CopyrightContent' } }
              label=""
            >
                <span block="Footer" elem="Copyright">
                    { copyright }
                    { ' Powered by ' }
                    <a href="https://scandipwa.com">
                        ScandiPWA
                    </a>
                </span>
            </ContentWrapper>
        );
    }

    render() {
        return (
            <footer block="Footer" aria-label="Footer">
                { this.renderContent() }
                { this.renderCopyrightContent() }
            </footer>
        );
    }
}

export default Footer;
```

{% endcode %}

{% hint style="info" %}
That funny [HTML-in-JavaScript syntax is called JSX](https://reactjs.org/docs/introducing-jsx.html). Scandi uses the React library to render its user interface, and JSX is the easiest way to use React.
{% endhint %}

Now that we have found the component, we can update its code. But **don't** edit the file we found in `node_modules` – modifying dependency code is almost always a bad idea. (It would be hard to get updates, and difficult to track which of the many files you have edited). Instead, let's override it.

## Overriding the Footer

Scandi offers a great way to customize any component, and its called the Override Mechanism. With the override mechanism, you can override any file you want, while keeping the default implementations for the other files. This gives you great flexibility without having to duplicate any code.

To override a file, you need to create a new file with the same path in your `src` directory. For example...

```
To override:
    component/Footer/Footer.component.js
    (in node_modules/@scandipwa/scandipwa/src/)

...you need to create a new file:
    component/Footer/Footer.component.js
    (in src/)
```

{% hint style="info" %}
Instead of manually creating a new file, you can save yourself a lot of time by using the [Scandi CLI](/developing-with-scandi/developer-tools/scandipwa-cli). With a single command, you can override the Footer component:

```bash
scandipwa override component Footer
```

When you run that command, the Scandi tool will ask you which components you want to override. Select the `Footer` class in `Footer.component.js`; leave the other fields blank:
{% endhint %}

![Using the Scandi CLI to override the Footer component](/files/-MZw0HC0-dlCAzgVAulz)

Now we have created the file, which overrides the original Footer component file. Now, we want to import the original class, so that we can keep most of the Footer functionality, and extend it to customize its behavior:

{% code title="File override in src/component/Footer/Footer.component.js" %}

```javascript
// We will need the ContentWrapper component later - let's import it
import ContentWrapper from 'Component/ContentWrapper';


// Import the original class (we want to keep most of the functionality)
// Note that we are using the "SourceComponent" alias in the import path –
// This tells Scandi that we want to get the original Footer component
import {
    Footer as SourceFooter
} from 'SourceComponent/Footer/Footer.component';


// Extend the original class (SourceFooter)
// By subclassing it, we can change some of its behavior
/** @namespace myFirstApp/Component/Footer/Component/FooterComponent */
export class FooterComponent extends SourceFooter {

    // This is the function responsible for rendering copyright
    // We want to change it, so we re-define in this subclass
    renderCopyrightContent() {
    
        // Changed:
        // Instead of the copyright text, let's write a friendly message
        return (
            <ContentWrapper
              mix={ { block: 'Footer', elem: 'CopyrightContentWrapper' } }
              wrapperMix={ { block: 'Footer', elem: 'CopyrightContent' } }
              label=""
            >
                <span block="Footer" elem="Copyright">
                    Thank you for visiting my website. You are amazing!
                </span>
            </ContentWrapper>
        );
    }
    
    // All the other functions will stay the same...
    // Because we didn't override any other default functionality
}

// All components, including the original Footer component, have a
// default export. Other files use this export when they want to use
// this component.
// Now, instead of providing the original component, we export our
// overridden component. Any file importing this will get the new behavior!
export default FooterComponent;
```

{% endcode %}

## Results

Scandi implements hot reload – which means that you don't need to compile the app again. Just check your browser and the changes should have appeared.

![🎉🎉](/files/-MZvsKkyhkmkbNVc4lY_)

Congratulations! Now you understand the basics of file overriding – and you can override any file in the app to change its behavior.


# Adding a New Page

Learn to create new pages (routes) in your Scandi app!

Suppose we wanted to create an "about us" page. Usually, we would do this by using the CMS system. However, for the sake of a simple example, suppose we want to hard-code this page in the codebase.

## Creating a New Route

First, we need a React component responsible for rendering the page. These are called routes, and in Scandi, they can be found within the `route` directory.

While you could create the necessary boilerplate yourself, the `scandipwa` [CLI](/developing-with-scandi/developer-tools/scandipwa-cli) utility can automate this for you. The `create route` command will create a new route. You can name yours anything you want, but we called it `AboutUs`:

```
scandipwa create route AboutUs
```

This should create a new subfolder under `route` and create all the necessary files inside. The command output will also tell us what happened:

```
NOTE!

     The following files have been created:
     src/route/AboutUs/AboutUs.component.js
     src/route/AboutUs/AboutUs.style.scss
     src/route/AboutUs/index.js
```

Let's create some basic placeholder content in the `.component.js` file:

```jsx
    render() {
        return (
            <div block="AboutUs">
                <h1>Don&apos;t Panic</h1>
                <p>
                    Welcome to my website.
                </p>
            </div>
        );
    }
```

Now, we have created our route. But it doesn't appear anywhere in our app yet... It's just another component lying around, unused, in our codebase. Let's do something about it!

## Registering the Route

In Scandi, the `Router` component is responsible for registering all routes. It uses `react-router` to specify which Routes should be visible in different URLs. Let's take a look at the original `Router` component. It contains a special field where it configures all of the routes available in the app:

```jsx
    [SWITCH_ITEMS_TYPE] = [
        {
            component: <Route path={ withStoreRegex('/') } exact render={ (props) => <HomePage { ...props } /> } />,
            position: 10
        },
        {
            component: <Route path={ withStoreRegex('/cart') } exact render={ (props) => <CartPage { ...props } /> } />,
            position: 50
        },
        {
            component: <Route path={ withStoreRegex('/checkout/:step?') } render={ (props) => <Checkout { ...props } /> } />,
            position: 55
        },
	      // [...]
    ];
```

Above, we can see that the field `[SWITCH_ITEMS_TYPE]` is an array containing all of the routes in the app. Each of them has a `component` field containing a `Route` component. Each of these components has two important props – the `path`, which is the domain-relative URI of the route, as well as the render function itself. For more details, see `react-router` [docs](https://reactrouter.com/web/guides/quick-start).

Now that we know where the routes are defined, we want to add our own to the list. In Scandi, all theme changes are made through the [Override Mechanism](/developing-with-scandi/override-mechanism/extending-javascript) – so let's override this Router!

If you wanted to do this manually, all you'd have to do is create a new component file in your theme `src` folder, matching the path of the original file. However, the `scandipwa` [CLI](/developing-with-scandi/developer-tools/scandipwa-cli) utility can help us automate this. Type `scandipwa override component Router` to override the router component. When asked what to extend in the `Router.component.js` file, select the `Router` class and nothing else. Leave the answers blank in the other questions.

```
scandipwa override component Router
? Choose things to extend in Router.component.js Router
? What would you like to do with styles? Keep
? Choose things to extend in Router.config.js 
? Choose things to extend in Router.container.js 

NOTE!

     The following files have been created:
     src/component/Router/Router.component.js
```

Now the file is overridden, but, by default, it exports everything as in the original file. First, copy-paste the original `[SWITCH_ITEMS_TYPE]` field inside your overridden class. Then, you can add your own route:

```jsx
    [SWITCH_ITEMS_TYPE] = [
        {
            component: <Route path={ withStoreRegex('/') } exact render={ (props) => <HomePage { ...props } /> } />,
            position: 10
        },
        {
            component: <Route path={ withStoreRegex('/about-us-page') } exact render={ (props) => <AboutUs { ...props } /> } />,
            position: 14
        },
	      // [...]
```

{% hint style="info" %}
Because of the code style in the current original Scandi code, you will need to disable some ESLint rules for your code to compile:

```jsx
/* eslint-disable react/jsx-no-bind */
/* eslint-disable @scandipwa/scandipwa-guidelines/no-jsx-variables */
/* eslint-disable max-len */
/* eslint-disable @scandipwa/scandipwa-guidelines/jsx-no-props-destruction */
```

Usually, you should avoid disabling ESLint rules unless necessary, but in this case, copying the original code style results in linting errors. In the future, we hope to improve code quality to avoid some of these rule violations.
{% endhint %}

We can now verify that the page is correctly registered and appears on the frontend:

![](/files/-MdWahI8Fej2DFEuN8-m)

While the page may work on the frontend, we still need to make changes to the backend code, so that it knows that `/about-us-page` is a valid URL. Currently, if you refresh `/about-us-page` and check the response status code from the server, you will see that it responds with a 404 Not Found code. While this may seem insignificant at first, it can actually hurt your SEO.

## Backend Adjustments

How do we configure the Magento router to register the route on the backend as well? The ScandiPWA `route717` module is responsible for resolving paths and determining if they are valid or not. If you add a new route, the `route717` module needs to know about it so that the correct response code is returned from the backend.

Looking at `scandipwa/route717/src/etc/di.xml`, we can see an example of how the routes can be configured:

```markup
<?xml version="1.0"?>
<config xmlns:xsi="<http://www.w3.org/2001/XMLSchema-instance>"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <!-- [...] -->
    <type name="ScandiPWA\\Router\\ValidationManager">
        <arguments>
            <argument name="validators" xsi:type="array">
                <item name="cart" xsi:type="string">ScandiPWA\\Router\\Validator\\AlwaysPass</item>
                <item name="wishlist" xsi:type="string">ScandiPWA\\Router\\Validator\\Wishlist</item>
                <item name="checkout" xsi:type="string">ScandiPWA\\Router\\Validator\\AlwaysPass</item>
            </argument>
        </arguments>
    </type>
    <!-- [...] -->
</config>
```

The `validators` argument of the `ValidationManager` is an array mapping paths to corresponding validators. The validators' job is to determine if a path is valid (200 status code) or invalid (404 not found).&#x20;

You can write your own custom validator if you need to – which you would have to do if you need to access the database to determine if the path is valid (for example, for products, you need to check if the product exists). However, in our case, all we need to do is add a simple `AlwaysPass` validator – because our `about-us-page` should always return a success status code, 200. First, create a new Magento module for this configuration. Then, it its `di.xml` file, add the following configuration:

```markup
<?xml version="1.0"?>
<config xmlns:xsi="<http://www.w3.org/2001/XMLSchema-instance>"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="ScandiPWA\\Router\\ValidationManager">
        <arguments>
            <argument name="validators" xsi:type="array">
                <item name="about-us-page" xsi:type="string">ScandiPWA\\Router\\Validator\\AlwaysPass</item>
            </argument>
        </arguments>
    </type>
</config>
```

Make sure the new module is registered by running `magento setup:upgrade`. Now, when you refresh the page, you should see that your custom page returns a successful status code.

## What's Next?

Congrats! You have now added a brand-new page to your Scandi app. As an exercise, you could try to add some more pages, perhaps with more complicated paths with variables (hint: see the `react-router` [docs](https://reactrouter.com/web/guides/quick-start)!). Or perhaps you want to add a few links that will lead to your new page? Use the [Link](https://reactrouter.com/web/api/Link) component for this purpose. You can also play around with your new page, adding more styling and content if you want.


# Adding a Section in My Account

Learn to override with a common pattern in Scandi – render maps

In Scandi, several UI elements, including the My Account page, are organized in tabs:

![](/files/-Me4WM8wC_aknts9pY7y)

In this tutorial, we will learn to customize the theme by adding a new tab to the My Account page. If you follow along with this tutorial, you should be able to add any component to any tab in Scandi.

## Creating a New Component

First, we need to create a component responsible for displaying the content of the tab. This means creating a new subdirectory in the `component` directory with the required files. Using the `scandipwa` CLI utility, this step can be automated with a single command.

We will name our example component `PositiveAffirmations`:

```bash
scandipwa create component PositiveAffirmations
```

This should create some new files and give the following output:

```
NOTE!

     The following files have been created:
     src/component/PositiveAffirmations/PositiveAffirmations.component.js
     src/component/PositiveAffirmations/PositiveAffirmations.style.scss
     src/component/PositiveAffirmations/index.js
```

Now, let's edit the `.component.js` file to render some example content. For simplicity, our example will contain positive self-affirmations copy-pasted from the internet:

{% code title="src/component/PositiveAffirmations/PositiveAffirmations.component.js" %}

```jsx
    render() {
        return (
            <div block="PositiveAffirmations">
                <p>{ __('Today, I am brimming with energy and overflowing with joy.') }</p>
                <p>{ __('I live in the moment while learning from the past and preparing for the future.') }</p>
                <p>{ __('Life is beautiful.') }</p>
            </div>
        );
    }
```

{% endcode %}

This will help customers gain confidence and boost their mood while they adjusting the settings for their account.

{% hint style="info" %}
Wondering what the `{ __("") }` magic is for? They are necessary to enable translating the text. You could remove them, and the app would still work, but only in English. Let's break down what that syntax does:

1. The curly braces `{}` allow us to escape from JSX (that HTML-like syntax) and write normal JavaScript expressions.
2. `__` is a special function-like directive in Scandi. It allows us to translate the content.
3. The text needs to be put in quotes `""` so that it can be processed like any other JavaScript string.
   {% endhint %}

Great! Now we have created the component, but it's not yet visible on the page, because we haven't used it anywhere – that's the next step.

## Inspecting the Codebase

To add a new tab, we need to find out which component is responsible for rendering all of the My Account tabs. Using [React Developer tools](/developing-with-scandi/developer-tools#browser), we can identify the `MyAccountTabList` component:

![](/files/-Me4Xc9hQ9VChzZedwSH)

Inspecting the source code and searching for usages of `<MyAccountTabList`, we find that the tabs are passed as props from `MyAccount.component.js`

{% code title="node\_modules/@scandipwa/scandipwa/src/route/MyAccount/MyAccount.component.js" %}

```jsx
    renderContent() {
        const {
            tabMap,
	          // ...
        } = this.props;
	      // ...

	const TabContent = this.renderMap[activeTab];
        const { name } = tabMap[activeTab];

        return (
            <ContentWrapper>
                <MyAccountTabList
                  tabMap={ tabMap }
                  activeTab={ activeTab }
                  changeActiveTab={ changeActiveTab }
                  onSignOut={ onSignOut }
                />
		            <div block="MyAccount" elem="TabContent">
                    <h2 block="MyAccount" elem="Heading">{ name }</h2>
                    <TabContent isEditingActive={ isEditingActive } />
                </div>
            </ContentWrapper>
        );
    }
```

{% endcode %}

The tab components themselves are specified in the `renderMap`:

{% code title="node\_modules/@scandipwa/scandipwa/src/route/MyAccount/MyAccount.component.js" %}

```jsx
    renderMap = {
        [DASHBOARD]: MyAccountDashboard,
        [MY_ORDERS]: MyAccountMyOrders,
        [MY_WISHLIST]: MyAccountMyWishlist,
        [ADDRESS_BOOK]: MyAccountAddressBook,
        [NEWSLETTER_SUBSCRIPTION]: MyAccountNewsletterSubscription,
        [MY_DOWNLOADABLE]: MyAccountDownloadable
    };
```

{% endcode %}

The metadata (such as the tab name), is passed to the component as a prop from `MyAccount.container.js`, where the `tabMap` is defined:

{% code title="node\_modules/@scandipwa/scandipwa/src/route/MyAccount/MyAccount.container.js" %}

```jsx
    tabMap = {
        [DASHBOARD]: {
            url: '/dashboard',
            name: __('Dashboard')
        },
        [ADDRESS_BOOK]: {
            url: '/address-book',
            name: __('Address book')
        },
        [MY_ORDERS]: {
            url: '/my-orders',
            name: __('My orders')
        },
        [MY_DOWNLOADABLE]: {
            url: '/my-downloadable',
            name: __('My downloadable')
        },
        [MY_WISHLIST]: {
            url: '/my-wishlist',
            name: __('My wishlist'),
            headerTitle: () => this.getMyWishlistHeaderTitle()
        },
        [NEWSLETTER_SUBSCRIPTION]: {
            url: '/newsletter-subscription',
            name: __('Newsletter Subscription')
        }
    };
```

{% endcode %}

We need to override the `.container` to add a new tab entry, but we also need to override the `.component` to specify how to render it.

## Adding a New Tab

As we found above, we need to override the `MyAccount` route to customize the tabs. Again, the `scandipwa` CLI can do this automatically for us. First, enter this command:

```
scandipwa override route MyAccount
```

Then, the `scandipwa` CLI will interactively ask you which parts of the `MyAccount` route you want to override. We only want to override the `MyAccountContainer` class in the `MyAccount.container.js` file, and the `MyAccount` class in the `.component` file; leave the other answers blank.

```
? Choose things to extend in MyAccount.component.js MyAccount
? What would you like to do with styles? Keep
? Choose things to extend in MyAccount.config.js 
? Choose things to extend in MyAccount.container.js MyAccountContainer

NOTE!

     The following files have been created:
     src/route/MyAccount/MyAccount.component.js
     src/route/MyAccount/MyAccount.container.js
```

The command will automatically create the boilerplate necessary for overriding the file. Now, it's up to us to make the necessary changes in these files.

First, open the `MyAccount.component` file and configure the `renderMap` to include our custom component.

{% code title="src/route/MyAccount/MyAccount.component.js" %}

```jsx
export const POSITIVE_AFFIRMATIONS = 'affirmations';

/** @namespace TutorialCsaApp/Route/MyAccount/Component/MyAccountComponent */
export class MyAccountComponent extends SourceMyAccount {
    renderMap = {
        [DASHBOARD]: MyAccountDashboard,
        [MY_ORDERS]: MyAccountMyOrders,
        [MY_WISHLIST]: MyAccountMyWishlist,
        [ADDRESS_BOOK]: MyAccountAddressBook,
        [NEWSLETTER_SUBSCRIPTION]: MyAccountNewsletterSubscription,
        [MY_DOWNLOADABLE]: MyAccountDownloadable,
        [POSITIVE_AFFIRMATIONS]: PositiveAffirmations // <-- added!
    };
}
```

{% endcode %}

Now, `MyAccountComponent` knows how to render our custom tab. We still need to configure the tab URL and title for it to appear in the list. For this, we can override the `tabMap` field of the `MyAccountContainer`.

{% code title="src/route/MyAccount/MyAccount.container.js" %}

```jsx
import { POSITIVE_AFFIRMATIONS } from './MyAccount.component';

/** @namespace TutorialCsaApp/Route/MyAccount/Container/MyAccountContainer */
export class MyAccountContainer extends SourceMyAccountContainer {
    tabMap = {
        [DASHBOARD]: {
            url: '/dashboard',
            name: __('Dashboard')
        },
        // [...] copy all the original entries from MyAccountContainer.tabMap here
	// (unless you want to intentionally remove them)

        [POSITIVE_AFFIRMATIONS]: { // <-- new entry - custom tab!
            url: '/affirmations',
            name: __('Positive Affirmations')
        }
    };
}
```

{% endcode %}

{% hint style="warning" %}
Scandi is very strict about code style, and you might have to fix some issues for the ESLint check to pass. Most issues can be automatically fixed by running `eslint --fix src`.
{% endhint %}

## Result

Let's check the brand-new My Account tab – as expected, it appears among the other tabs!

![](/files/-Me4fXsWqu5akRv6i1b0)

Congrats! You have now learned to add new tabs to Scandi components. And you will find that similar patterns with `renderMap` can be overridden in the same way – you can add, remove, or modify which components are rendered by configuring an object or array.


# Adding a Tab on the Product Page

Another example of overriding a tab configuration

In this quick guide, you will learn about adding a tab to the product page. As you'll see, it will be very similar to the previous tutorial, as it uses the same pattern.&#x20;

## Inspecting the Codebase

First, we need to find the code responsible for rendering the product page tabs. You can use the [React developer tools extension](/developing-with-scandi/developer-tools#browser) to find which components are being rendered. Searching the codebase can also be useful.

We find that the `ProductPage` route is itself responsible for configuring the tabs:

{% code title="node\_modules/@scandipwa/scandipwa/src/route/ProductPage/ProductPage.component.js" %}

```jsx
    tabMap = {
        [PRODUCT_INFORMATION]: {
            name: __('About'),
            shouldTabRender: () => {
                const { isInformationTabEmpty } = this.props;
                return isInformationTabEmpty;
            },
            render: (key) => this.renderProductInformationTab(key)
        },
        [PRODUCT_ATTRIBUTES]: {...},
        [PRODUCT_REVIEWS]: {...}
    };
```

{% endcode %}

Now, we want to add our own tab to the mix!

## Adding a New Tab

First, let's override the `.component` file of the `ProductPage` route. You can easily do this using the `scandipwa` [CLI](/developing-with-scandi/developer-tools/scandipwa-cli):

```bash
scandipwa override route ProductPage
```

Make sure to select the `ProductPage` class in the `.component` file, because this is where we want to make modifications. You can also extend the styles if you intend to update them.

```
? Choose things to extend in ProductPage.component.js ProductPage
? What would you like to do with styles? Extend
? Choose things to extend in ProductPage.config.js 
? Choose things to extend in ProductPage.container.js 

NOTE!

     The following files have been created:
     src/route/ProductPage/ProductPage.component.js
     src/route/ProductPage/ProductPage.override.style.scss
```

Now, jump into the `.component` file and update the `tabMap` by adding your own tab.

{% code title="src/route/ProductPage/ProductPage.component.js" %}

```jsx
import {
    PRODUCT_ATTRIBUTES,
    PRODUCT_INFORMATION,
    PRODUCT_REVIEWS
} from 'Route/ProductPage/ProductPage.config';
import {
    ProductPage as SourceProductPage
} from 'SourceRoute/ProductPage/ProductPage.component';

import './ProductPage.override.style.scss';

/** @namespace TutorialCsaApp/Route/ProductPage/Component/ProductPageComponent */
export class ProductPageComponent extends SourceProductPage {
    tabMap = {
        [PRODUCT_INFORMATION]: {
            name: __('About'),
            shouldTabRender: () => {
                const { isInformationTabEmpty } = this.props;
                return isInformationTabEmpty;
            },
            render: (key) => this.renderProductInformationTab(key)
        },
        [PRODUCT_ATTRIBUTES]: {
            name: __('Details'),
            shouldTabRender: () => {
                const { isAttributesTabEmpty } = this.props;
                return isAttributesTabEmpty;
            },
            render: (key) => this.renderProductAttributesTab(key)
        },
        GUARANTEE_TAB: { // <-- we added a new tab here
            name: __('Guarantee'),
            shouldTabRender: () => false,
            render: (key) => this.renderGuarantee(key)
        },
        [PRODUCT_REVIEWS]: {
            name: __('Reviews'),
            shouldTabRender: () => false,
            render: (key) => this.renderProductReviewsTab(key)
        }
    };

    // the function responsible for rendering the tab content
    renderGuarantee(key) {
        console.log(key);
        return (
            <section block="ProductPage" elem="Guarantee" key={ key }>
                <h1>{ __('Our Promise to You') }</h1>
                <p>
                    { __('Everything we make is guaranteed to work for at least two years. It cannot break. If it'
                        + ' breaks it\\'s probably your fault.') }
                </p>
            </section>
        );
    }
}
```

{% endcode %}

## Result

If you check the product page in your app, you'll see that the tabs are now updated, and include our new tab:

![](/files/-Me9tj8Bt5VjP5kPqD9Y)


# Creating a New Redux Store

Learn to add your Redux store to the Scandi app

In this guide, you will learn to add a new Redux store to your app. To learn more about [Redux stores in Scandi](/structure/building-blocks-summary/redux-stores), read the docs.

## Creating a New Store

First, we need to create a new Redux store. Using the [`scandipwa`](/developing-with-scandi/developer-tools/scandipwa-cli) CLI utility, you can easily create a new store with a single command. In our toy example, we will call our store `ImportantNumber`:

```bash
scandipwa create store ImportantNumber
```

You should see that new files have been created, as indicated by a success message:

```
NOTE!

     The following files have been created:
     src/store/ImportantNumber/ImportantNumber.action.js
     src/store/ImportantNumber/ImportantNumber.reducer.js
```

Feel free to implement any logic you need in the Redux store.

## Redux in Scandi

At this point, you will notice that while we have defined the store in the codebase, it is not registered in the app yet. This is because it hasn't been registered yet.

In Scandi, stores are registered in the `store/index.js` file:

{% code title="node\_modules/@scandipwa/scandipwa/src/store/index.js" %}

```javascript
import BreadcrumbsReducer from 'Store/Breadcrumbs/Breadcrumbs.reducer';
import CartReducer from 'Store/Cart/Cart.reducer';
import CategoryReducer from 'Store/Category/Category.reducer';
// [...]

/** @namespace Store/Index/getReducers */
export const getStaticReducers = () => ({
    BreadcrumbsReducer,
    CartReducer,
    CategoryReducer,
    // [...]
});

export default function injectStaticReducers(store) {
    Object.entries(getStaticReducers()).forEach(
        ([name, reducer]) => store.injectReducer(name, reducer)
    );

    return store;
}
```

{% endcode %}

As you can see, most reducers are registered in the `getStaticReducers` function. To add a new reducer, we must override this function, and add our own reducer. We must also override `injectStaticReducers` to use our new function.

{% hint style="info" %}
Confused by the object notation syntax? In JavaScript, spelling out the key name is optional if it's the same as the value variable. So writing...

```javascript
{
    BreadcrumbsReducer,
    CartReducer,
    CategoryReducer
}
```

...is the same as writing:

```javascript
{
    'BreadcrumbsReducer': BreadcrumbsReducer,
    'CartReducer': CartReducer,
    'CategoryReducer': CategoryReducer
}
```

It's just like any other JavaScript object `{}`.
{% endhint %}

## Registering the Store

So how can we override the `index.js` file? Unfortunately, at the moment, the CLI cannot automate this for us, so you need to create a new file for overriding manually:

{% code title="src/store/index.js" %}

```javascript
// import the original getStaticReducers function
// and rename it to baseGetStaticReducers
import { getStaticReducers as baseGetStaticReducers } from 'SourceStore/index';

// import our own reducer
import ImportantNumberReducer from 'Store/ImportantNumber/ImportantNumber.reducer';

// define getStaticReducers.
// this is a function that returns an object of all the reducers in the app.
// just like in the base theme...
/** @namespace TutorialCsaApp/Store/Index/getStaticReducers */
export const getStaticReducers = () => ({
    ...baseGetStaticReducers(),
    
    // ...except we also add our own reducer to the mix
    ImportantNumberReducer
});

// nothing new here, just copying the function from the base theme
// (this is necessary so that it uses our own `getStaticReducers` function
export default function injectStaticReducers(store) {
    Object.entries(getStaticReducers()).forEach(
        ([name, reducer]) => store.injectReducer(name, reducer)
    );

    return store;
}
```

{% endcode %}

Aaand that's it! If you check the Redux developer tools, you will see that your reducer is now included in the state.


# Payment Method Integration

Integrate an existing payment method so you can use it in a Scandi store!

A crucial part in any Magento website – payment methods! In the Magento Marketplace you can find a large number of payment method providers – but adapting them to work with Scandi can require additional effort (unless the integration already exists on the Scandi Marketplace).

In this tutorial, you'll learn the basics of building an extension for integrating a new payment method in Scandi.

For our example, we'll use Mollie. Like many other providers, Mollie redirects the user to another page after checkout to complete the payment. After the customer has entered their details on the 3rd party site, they are redirected back to the website, where they see a message about the current payment status. We'll need to adapt this logic so it also works in a Scandi theme.


# Setting Up for Development

Before you can start working on the Scandi integration, you need to set up a local environment

## Creating a New App

First, make sure you have a working Magento app with Scandi installed. The easiest way to get this setup is by using `create-magento-app` with `create-scandipwa-app`!

Next, since we want to create a reusable extension that can be installed on multiple projects, we can initialize it with a single command. After checking that you have the `scandipwa` CLI utility installed, navigate to your theme's root directory and enter this command:

(don't forget to replace `your-payment-provider` with the actual payment provider name!)

```
scandipwa extension create your-payment-provider-scandi
```

This will automatically create a new extension, including some boilerplate configuration to get you started. It will also install the extension into your app.

## Setting up the Payment Extension

In most cases, you'll also want to install the Magento payment integration extension. While it might not provide out-of-the-box support for working with Scandi, it will already include most of the payment logic that you need for the integration.

Next, you'll need to configure the extension. The configuration will vary across providers, but you'll most likely have to log in to your account on the payment provider's website to copy some credentials into your Magento configuration – check the extension's documentation for more details!

In our example, Mollie needs us to enter a few authentication secrets to connect the extension to the Mollie account. We also need to enable the different payment methods supported by Mollie.

Be sure to also enable test mode in the extension. This will allow you to ensure your extension is working correctly without needing to make real payments.

It might be a good idea to get an idea of how the Magento extension works before starting work on your Scandi integration. You could simply switch the theme to Luma and complete a

## *Sigh...* Webhooks

Many payment workflows involve webhooks. Webhooks allow a third-party server (in this case the payment provider) to notify your Magento app when something of interest has happened – for example, when a payment has been completed or canceled.

Crucially, since the payment providers' servers need to be able to notify your app, the URL needs to be accessible to them. This means that you can't simply keep your app on `localhost`, because then your computer is the only one that can access the website. And of course, the payment provider isn't running on your computer, so they can't access your app.

Fortunately, there is a workaround! You can tunnel traffic from a publicly accessible address to your locally-running app. `ngrok` is a tool that allows you to do it for free.

### Using ngrok

First, [create a ngrok account](https://ngrok.com/), and install the ngrok tool. Configure ngrok as specified in the instructions on their website. Then, you should be able to create a tunnel from a public `.ngrok.io` URL to your localhost. The easiest way is to create a HTTP tunnel:

```
./ngrok http 80 --region eu
```

Note: the `--region` parameter is optional, but your requests might be faster if you specify a region that is close to where you are.

Now, your local app will be available to anyone that knows the `ngrok` URL – which will be displayed after running the above command. And "anyone" includes the payment service provider, so webhooks will work!

Don't forget to update the Magento `base_url` to the new ngrok URL. Otherwise, you'll keep getting redirected to your [localhost](http://localhost) address.


# Redirecting to the Payment Provider

Redirect to a 3rd party site after the order is placed

Very often, after the shipping details are entered and the payment method is chosen on your website, the user needs to be redirected to a third-party site to complete the payment. You can easily do this in Scandi by writing a few plugins.

Note: In this example, we're implementing a specific payment method provider, Mollie. If you are making an extension for a different payment service, the steps outlined below might be slightly different. However, the general workflow of integrating a payment method will be similar: inspect the checkout source code and customize it through plugins.

## Getting the Redirection URL

First, we need to get the URL that the user should be redirected to. For many payment extensions, this URL is included in the `placeOrder` GraphQL mutation response. Indeed, our example extension, Mollie, includes a `mollie_redirect_url` in the `order` field of the response.

However, as you may know, in GraphQL, only the fields that have been requested are found in the response. And of course, since `mollie_redirect_url` is an extension-specific field, it is not requested by default. Therefore, we need to write a plugin that will request this additional field as part of the `placeOrder` mutation.

After inspecting the Scandi source code, you'll find that the file responsible for creating the `placeOrder` mutation is `scandipwa/src/query/Checkout.query.js`.

All Scandi query creators live in the `query` folder. If you need to find the file responsible for a specific query, you can search for this query's name there.

First, let's take a look at the relevant parts of the original`Checkout.query` file, to get an idea of what we're plugging in to:

{% code title="scandipwa/src/query/Checkout.query.js" %}

```jsx
import { isSignedIn } from 'Util/Auth';
import { Field } from 'Util/Query';

/** @namespace Query/Checkout */
export class CheckoutQuery {
    // ...
    getPlaceOrderMutation(guestCartId) {
        const mutation = new Field('s_placeOrder')
            .setAlias('placeOrder')
            .addField(this._getOrderField());

        if (!isSignedIn()) {
            mutation.addArgument('guestCartId', 'String', guestCartId);
        }

        return mutation;
    }
		// ...

    _getOrderField() {
        return new Field('order')
            .addFieldList(['order_id']);
    }

    // ...
}

export default new CheckoutQuery();
```

{% endcode %}

As you see, the `_getOrderField` function is responsible for creating the `order` field, and populating it with the `order_id` subfield. We want to plug into this function to add an additional subfield – `mollie_redirect_url`.

So let's create a new plugin file! In the `plugin` directory of your extension, create a new Javascript file that ends in `.plugin.js`. It's a good idea to name it after the query it plugs into, so we'll name ours `Checkout.query.plugin.js`.

In Scandi, plugins are functions that wrap around the original function (read the documentation for more details of how they work). In this case, it's really simple:

{% code title="plugin/Checkout.query.plugin.js" %}

```jsx
const _getOrderField = (args, callback, instance) => {
		// `callback` is the original function.
		// By calling it, we get the original `order` field,
		// which only contains 1 subfield, `order_id`, as seen above
    return callback()
        .addFieldList([
            'mollie_redirect_url', // we add the redirect URL field
            'mollie_payment_token' // and also this one (we'll need it later)
        ])
}

// This bit is the plugin configuration object.
export default {
		// First, specify the namespace to plug in to.
    "Query/Checkout": {
				// There are different types of plugins, but since we're
				// plugging into a member function (as opposed to static functions)
				// we specify the "member-function" type
        "member-function": {
						// There's only 1 plugin we want to specify here
            _getOrderField: _getOrderField
						// (But we could plug into other functions too if we wanted!)
        }
    }
}
```

{% endcode %}

If all goes smoothly, you should see a `mollie_redirect_url` value returned in the GraphQL response when completing an order with a Mollie payment method.

You can check the "Network" tab in your browser's developer tools to see the GraphQL responses. This can be useful for debugging requests!

Now, we have the redirect URL in the response, but we're not doing anything with it yet. The next step is to implement the actual redirection functionality.

## Redirecting the User

First we need to find out which code is responsible for taking the user through the checkout steps. As the React Developer Tools extension will tell you, it's the `Checkout` route. We also know that the business logic (which is what we're interested in right now) most likely lives in the `.container`. So let's look at `scandipwa/src/route/Checkout/Checkout.container.js` , and the `savePaymentMethodAndPlaceOrder` function in particular:

{% code title="scandipwa/src/route/Checkout/Checkout.container.js" %}

```javascript
// [imports...]
// [mapStateToProps and mapDispatchToProps]

/** @namespace Route/Checkout/Container */
export class CheckoutContainer extends PureComponent {
    // [...]

    async savePaymentMethodAndPlaceOrder(paymentInformation) {
        const { paymentMethod: { code, additional_data } } = paymentInformation;
        const guest_cart_id = !isSignedIn() ? getGuestQuoteId() : '';

        try {
            await fetchMutation(CheckoutQuery.getSetPaymentMethodOnCartMutation({
                guest_cart_id,
                payment_method: {
                    code,
                    [code]: additional_data
                }
            }));

            const orderData = await fetchMutation(CheckoutQuery.getPlaceOrderMutation(guest_cart_id));
            const { placeOrder: { order: { order_id } } } = orderData;

            this.setDetailsStep(order_id);
        } catch (e) {
            this._handleError(e);
        }
    }

    render() {...}
}

export default connect(mapStateToProps, mapDispatchToProps)(CheckoutContainer);
```

{% endcode %}

The function sets the payment method for the order, and places the order. After the order is placed, it simply transitions to the order success step (also known as the "details step").

We want to change this functionality – instead of transitioning to the details step, we need to redirect the user to the final payment step, on the 3rd party website. We already made sure that `CheckoutQuery.getPlaceOrderMutation` will return the redirection URL in the response, so we just need to use that.

Let's write a plugin for `savePaymentMethodAndPlaceOrder` that will redirect the user if necessary. We can add it to our existing `Checkout.container.plugin.js` file:

{% code title="plugin/Checkout.container.plugin.js" %}

```jsx
import { redirectToUrl } from '../util/Redirect';
import { getPaymentToken, setPaymentToken } from '../util/PaymentTokenPersistence';

const savePaymentMethodAndPlaceOrder = async (args, callback, instance) => {
    const [paymentInformation] = args;

    const { paymentMethod: { code, additional_data } } = paymentInformation;
    const guest_cart_id = !isSignedIn() ? getGuestQuoteId() : '';

		// It's important to check if the user has selected a Mollie payment method.
		// We don't want to affect other methods, so we just use the callback directly in that case.
    if (!MOLLIE_METHODS.includes(code)) {
        return await callback(...args);
    }
		// Since this is a Mollie method, now we need some custom logic

    try {
				// Just like the original function, we set the payment method
        await fetchMutation(CheckoutQuery.getSetPaymentMethodOnCartMutation({
            guest_cart_id,
            payment_method: {
                code,
                [code]: additional_data,
            },
        }));

				// However, when placing the order, there is some additional data we need to consider
        const orderData = await fetchMutation(CheckoutQuery.getPlaceOrderMutation(guest_cart_id));
        const { placeOrder: { order: { mollie_redirect_url, mollie_payment_token } } } = orderData;

        if (Boolean(mollie_payment_token)) {
            // We'll need this token later, so let's save it to the browser's storage
            setPaymentToken(mollie_payment_token);
        } else {
						// It is a good idea to "fail early" if some data you expect to be present
						// is not there. Also, provide good error messages.
						// That way, your code will be easier to debug if something goes wrong.
            throw Error("Expected mollie_payment_token in order data, none found", orderData)
        }

				// We take the redirect URL we requested in the previous step and redirect to it
        if (Boolean(mollie_redirect_url)) {
            redirectToUrl(mollie_redirect_url)
        } else {
            throw Error("Expected mollie_redirect_url in order data, none found", orderData)
        }
    } catch (e) {
        instance._handleError(e);
    }
};

// Afain, we need to export the plugin configuration:
export default {
    "Route/Checkout/Container": {
        "member-function": {
            savePaymentMethodAndPlaceOrder,
        },
    },
}
```

{% endcode %}

As you might have noticed, we used a couple of custom utility functions, namely `redirectToUrl` and `setPaymentToken`. Of course we need to define them as well.

It's common for extensions to need new utility functions. They just need to implement them in the `util` directory, just like you would in a theme.

The `redirectToUrl` in `Redirect.js` is a really simple function that I found on StackOverflow (don't tell anyone). Still, it's nice to have it as a reusable function so the rest of the code is more readable.

{% code title="util/Redirect.js" %}

```javascript
export const redirectToUrl = (url) => {
    window.location.replace(url);
};
```

{% endcode %}

The `setPaymentToken` and `getPaymentToken` functions (we haven't used the latter yet) are also very simple. They use the `BrowserDatabase` utility to save (and load) the payment token. This token will still be accessible after redirecting the user to the payment provider's page and back.

{% code title="util/PaymentTokenPersistence.js" %}

```jsx
// Let's use Scandi's BrowserDatabase utility for persistence
import BrowserDatabase from 'Util/BrowserDatabase';
import { ONE_MONTH_IN_SECONDS } from 'Util/Request/QueryDispatcher';

const TOKEN_KEY = 'mollie_payment_token';

export const setPaymentToken = (token) => {
		// Again, some error checking can help catch mistakes early.
    if (!Boolean(token)) {
        throw Error("Must specify token to set")
    }

    BrowserDatabase.setItem(token, TOKEN_KEY, ONE_MONTH_IN_SECONDS);
};

export const getPaymentToken = () => {
    const token = BrowserDatabase.getItem(TOKEN_KEY);

    if (!Boolean(token)) {
        throw Error("No payment token found in browser database")
    }

    return token
};
```

{% endcode %}

And with that, the redirection step is complete! If you wish, you can complete an order using Mollie's payment methods to verify that you are indeed redirected.


# Handling the Customer's Return

Handle the customer's return from a 3rd party site

After the customer has entered the payment details on the 3rd party page, they are returned to the e-commerce store. Often, there is additional logic required in this step. For example, you might have to fetch the transaction result to display it to the user.

In our case, we need to call the `mollieProcessTransaction` GraphQL mutation to make sure the transaction is processed, and to retrieve the order result (success/failure, etc.). Finally, we need to display this result to the user, in the form of a success or failure message.

## Setting the Return URL

First, we need to ensure that the correct return URL is used. Since all Scandi checkout steps start with `checkout`, we're going to use `checkout/mollie_result` for the final order processing and displaying the result.

Mollie allows us to do this really easily by configuring some database values:

```php
<?php
namespace ScandiTutorials\\MollieScandiConfig\\Setup;

use Magento\\Framework\\App\\Config\\ConfigResource\\ConfigInterface;
use Magento\\Framework\\Setup\\InstallDataInterface;
use Magento\\Framework\\Setup\\ModuleContextInterface;
use Magento\\Framework\\Setup\\ModuleDataSetupInterface;
use Mollie\\Payment\\Config;

class InstallData implements InstallDataInterface
{
    /** @var ConfigInterface */
    protected $config;

    public function __construct(ConfigInterface $config) {
        $this->config = $config;
    }

    public function install(
			ModuleDataSetupInterface $setup, ModuleContextInterface $context
		) {
       $setup->startSetup();
       $this->config->saveConfig(Config::GENERAL_USE_CUSTOM_REDIRECT_URL, true);
       $this->config->saveConfig(
           Config::GENERAL_CUSTOM_REDIRECT_URL,
           '{{secure_base_url}}checkout/mollie_result?order_id={{increment_id}}&mollie_payment_token={{payment_token}}&mollie_order_hash={{order_hash}}'
       );
        $setup->endSetup();
    }
}
```

Now, we need to make sure that the correct business logic takes place after this redirect.

## Processing the Order

When the user is redirected back, we need to use the `mollieProcessTransaction` GraphQL mutation to process the order and get the transaction result.

First, let's take a look at some relevant methods in the `CheckoutContainer`, which renders all `checkout` routes, and thus will be involved in `checkout/mollie_result` as well:

{% code title="scandipwa/src/route/Checkout/Checkout.container.js" %}

```jsx
// [imports...]
// [mapStateToProps and mapDispatchToProps]

/** @namespace Route/Checkout/Container */
export class CheckoutContainer extends PureComponent {
    // [...]

__construct(props) {plplu
        super.__construct(props);

        const {
            toggleBreadcrumbs,
            totals: {
                is_virtual
            }
        } = props;

        toggleBreadcrumbs(false);

        this.state = {
            isLoading: is_virtual,
            isDeliveryOptionsLoading: false,
            requestsSent: 0,
            paymentMethods: [],
            shippingMethods: [],
            shippingAddress: {},
            checkoutStep: is_virtual ? BILLING_STEP : SHIPPING_STEP,
            orderID: '',
            paymentTotals: BrowserDatabase.getItem(PAYMENT_TOTALS) || {},
            email: '',
            isGuestEmailSaved: false,
            isCreateUser: false,
            estimateAddress: {}
        };

        if (is_virtual) {
            this._getPaymentMethods();
        }
    }

    componentDidMount() {
        const {
            history,
            showInfoNotification,
            totals: {
                items = []
            }
        } = this.props;

        if (!items.length) {
            showInfoNotification(__('Please add at least one product to cart!'));
            history.push(appendWithStoreCode('/cart'));
        }
    }

    // [...]

    render() {...}
}

export default connect(mapStateToProps, mapDispatchToProps)(CheckoutContainer);
```

{% endcode %}

We'll need to change the behavior of `__construct`, because otherwise it would cause problems by setting an incorrect initial state – such as `BILLING_STEP` or `SHIPPING_STEP` – but we actually want a custom `MOLLIE_PROCESSING_STEP` to be set.

For this, we can write a plugin. The function is very similar to the original one, but sets the correct initial step if applicable:

{% code title="plugin/Checkout.component.plugin.js" %}

```jsx
// We'll define this later...
// MOLLIE_PROCESSING_STEP is a simple string that identifies the current checkout step
import { MOLLIE_PROCESSING_STEP } from './Checkout.component.plugin';

const __construct = (args, callback, instance) => {
    const [props] = args;
    const {
        toggleBreadcrumbs,
    } = props;

    const { orderId, paymentToken, orderHash } = getParameters();
		// This is how we determine that it's a Mollie return URL:
		// The mollie_payment_token is specified
    if (!paymentToken) {
				// Not a return URL, just call the original function
        return callback(...args)
    }
		// Otherwise, we make sure to *not* call the original function
		// or else it would interfere with our logic

    toggleBreadcrumbs(false);

    instance.state = {
        isLoading: false,
        isDeliveryOptionsLoading: false,
        requestsSent: 0,
        paymentMethods: [],
        shippingMethods: [],
        shippingAddress: {},
        checkoutStep: MOLLIE_PROCESSING_STEP, // Set a custom step
        orderID: orderId,
        paymentTotals: BrowserDatabase.getItem(PAYMENT_TOTALS) || {},
        email: '',
        isGuestEmailSaved: false,
        isCreateUser: false,
        estimateAddress: {},
        mollieParameters: { orderId, paymentToken, orderHash },
        mollie: { isLoading: true },
    };
};
```

{% endcode %}

We'll also plug into `componentDidMount`, since that's a good place to put business logic that needs to run once when the component is initialized. The original function has some logic that we want to suppress for the Mollie processing step, so we'll check before calling it.

{% code title="plugin/Checkout.component.plugin.js" %}

```jsx
const componentDidMount = (args, callback, instance) => {
    const { mollieParameters: { paymentToken: isMolliePayment } = {} } = instance.state;

		// Check if it's a Mollie Payment return URL.
    if (!isMolliePayment) {
				// If not, just call the original function.
        return callback(...args)
    }

    const paymentToken = getPaymentToken();
    const processTransactionMutation = MollieQuery.getProcessTransactionMutation(paymentToken);

		// Make the GraphQL mutation for processing the transaction
    fetchMutation(processTransactionMutation).then(({ mollieProcessTransaction: { paymentStatus, cart } }) => {
        instance.setState({
            mollie: {
                isLoading: false,
                paymentStatus,
                cart,
            },
        })
    }).catch((e) => {
        console.error(e);
        instance.setState({
            mollie: {
                isLoading: false,
                paymentStatus: ERROR,
                cart: null,
            },
        })
    })
};
```

{% endcode %}

The above plugin makes use of the `MollieQuery` class. That's a query creator utility for `mollieProcessTransaction` which we also have to define:

{% code title="query/Mollie.query.js" %}

```jsx
import { Field } from 'Util/Query';

/** @namespace Query/Mollie */
export class MollieQuery {
    getProcessTransactionMutation(paymentToken) {
        if (!paymentToken) {
            throw Error("The payment_token is required")
        }

        return new Field('mollieProcessTransaction')
            .addArgument('input', 'MollieProcessTransactionInput', { payment_token: paymentToken })
            .addFieldList(['paymentStatus', this._getCartField()])
    }

    _getCartField() {
        return new Field('cart').addField('id')
    }
}

export default new MollieQuery()
```

{% endcode %}

Finally, don't forget to update the `.container` plugin configuration:

```javascript
export default {
    "Route/Checkout/Container": {
        "member-function": {
            __construct,
            componentDidMount,
            savePaymentMethodAndPlaceOrder,
        },
    },
}
```

## Displaying the Result

After the order has been processed, we need to inform the user of the order status.

{% code title="util/PaymentStatus.js" %}

```javascript
// This list was found in Mollie's backend code.
// We define the possible statuses so we can reuse them.
export const CREATED = 'CREATED';
export const PAID = 'PAID';
export const AUTHORIZED = 'AUTHORIZED';
export const CANCELED = 'CANCELED';
export const ERROR = 'ERROR';

export const ALL_SUCCESS = [CREATED, PAID, AUTHORIZED]

// Provides a human-readable, translated string describing the order status.
export const getStatusMessage = (status) => {
    switch (status) {
        case CREATED:
            return __('Your order has been created.');
        case PAID:
            return __('Your order has been paid.');
        case AUTHORIZED:
            return __('Your order has been authorized.');
        case CANCELED:
            return __('Your order has been canceled.');
        case ERROR:
            return __('There was an error processing your order.')
    }
};
```

{% endcode %}

Now, we can add plugins for the Checkout `.component`, which is responsible for the presentation logic of the checkout flow.

```jsx
import { ALL_SUCCESS, getStatusMessage } from '../util/PaymentStatus';
import CheckoutSuccess from 'Component/CheckoutSuccess';
import Loader from 'Component/Loader';

// A string representing the Mollie Processing Step
export const MOLLIE_PROCESSING_STEP = 'MOLLIE_PROCESSING_STEP';

function renderMollieStep() {
    const { mollie: { isLoading, paymentStatus } = {}, orderID } = this.props;

		// Display a loader while the order is processing
    if (isLoading) {
        return <Loader isLoading/>
    }

		// If the order was a success, render the CheckoutSuccess component
		// It renders a "continue shopping" button and the order ID
    if (ALL_SUCCESS.includes(paymentStatus)) {
        return (
            <CheckoutSuccess
                orderID={ orderID }
            />
        );
    }

    return false;
}

// We need to plug into the `stepMap` so we can configure it to handle our custom step
const stepMap = (member, instance) => ({
    ...member,
    [MOLLIE_PROCESSING_STEP]: {
        title: __('Loading'),
        url: '/mollie_result',
        render: renderMollieStep.bind(instance),
        areTotalsVisible: false,
    },
});

// We also customize the title
const renderTitle = (args, callback, instance) => {
    const { checkoutStep, mollie: { isLoading, paymentStatus } = {} } = instance.props;

		// Only handle our custom step; use the original function for everything else
    if (checkoutStep !== MOLLIE_PROCESSING_STEP) {
        return callback(...args);
    }

    if (isLoading) {
        return (
            <h2 block="Checkout" elem="Title">
                { __("Loading, please wait") }
            </h2>
        );
    }

    const message = getStatusMessage(paymentStatus);

    return (
        <h2 block="Checkout" elem="Title">
            { message }
        </h2>
    );
};

// Plugin configuration as always.
export default {
    'Route/Checkout/Component': {
        'member-property': {
            stepMap,
        },
        'member-function': {
            renderTitle,
        },
    },
}
```

## Conclusion

At this point, we've covered the main steps in integrating a payment method extension in Scandi. Usually, you need to do some debugging to make sure everything is working properly – payment integration can be tricky! You should be able to test your extension using the test mode of the payment provider.

When you're done with the extension, consider publishing it on the ScandiPWA Marketplace so others can benefit from your creation!


# Creating a Custom Widget

Create a widget you can use anywhere in the Magento CMS!

> A [widget](https://docs.magento.com/m2/ce/user_guide/cms/widgets.html) is a prepared snippet of code that can be used to place blocks, links, and dynamic content at specific locations on store pages. You can use widgets to create landing pages for marketing campaigns, display promotional content at specific locations throughout the store. Widgets can also be used to add interactive elements and action blocks for external review systems, video chats, voting, and subscription forms, or to provide navigation elements for tag clouds and image sliders.
>
> *–* [*Magento Glossary*](https://glossary.magento.com/widget/)

Since most of the Scandi presentation logic happens on the frontend, the CMS system is a little different in Scandi than default Magento. For this reason, you need to take a slightly different approach when developing a widget for Scandi.

In this example, we will create a newsletter widget. The CMS author will be able to add the widget anywhere and specify its title. Then, the customer will see the title and newsletter block on the frontend.


# Scandi CMS System Overview

An overview of how the Scandi CMS system works with widgets.

First, let's get familiar with how the widget system works in Magento+Scandi. This will give you a high-level understanding on what bits of code need to be added to create a new widget.

{% hint style="info" %}
Reading this section is optional, and only needed if you want to get a high-level understanding for the context of the code we'll be writing.
{% endhint %}

## The Widget System

First, let's get familiar with how the widget system works in Magento+Scandi. As an example, suppose the Admin creates a home page CMS page with a `RecentlyViewed` widget. The CMS source code would contain a `{{widget}}` template:

```markup
<p>Some CMS text...</p>
<p>{{widget type="Magento\\Catalog\\Block\\Widget\\RecentlyViewed" uiComponent="widget_recently_viewed" page_size="5" show_attributes="name,image,price,learn_more" show_buttons="add_to_cart" template="product/widget/viewed/grid.phtml"}}</p>
```

When rendering the page, the Magento CMS system will parse the `widget` template, and convert it into a widget element:

```markup
<p>Some CMS text...</p>
<p>
    <widget type='RecentlyViewed' uiComponent='widget_recently_viewed' page_size='5'
            show_attributes='name,image,price,learn_more' show_buttons='add_to_cart'></widget>
</p>
```

On the frontend, Scandi will parse this element. Let's dive into the parsing logic.

### The `Html` Component

In Scandi, the `Html` component is responsible for rendering all CMS content, including CMS pages and product descriptions. Instead of merely outputting the raw HTML, it parses its input. Then, some of the elements are replaced by React components. This means that CMS content can contain components just like any other part of the app!

Take a look at this simplified version of the HTML component:

{% code title="node\_modules/@scandipwa/scandipwa/src/component/Html/Html.component.js" %}

```jsx
import parser from 'html-react-parser';
import attributesToProps from 'html-react-parser/lib/attributes-to-props';
import domToReact from 'html-react-parser/lib/dom-to-react';
import PropTypes from 'prop-types';
import { PureComponent } from 'react';

import Image from 'Component/Image';
import Link from 'Component/Link';
import WidgetFactory from 'Component/WidgetFactory';
import { hash } from 'Util/Request/Hash';

/** @namespace Component/Html/Component */
export class Html extends PureComponent {
    static propTypes = {
        content: PropTypes.string.isRequired
    };

    rules = [
        {
            query: { name: ['widget'] },
            replace: this.replaceWidget
        },
        {
            query: { name: ['a'] },
            replace: this.replaceLinks
        },
        {
            query: { name: ['img'] },
            replace: this.replaceImages
        } // [...]
    ];

    parserOptions = {
        replace: (domNode) => {
            // [...] logic for replacing elements with components
        }
    };

    attributesToProps(attribs) {...}

    replaceLinks({ attribs, children }) {
        const { href, ...attrs } = attribs;

        return (
            <Link { ...attributesToProps({ ...attrs, to: href }) }>
                { domToReact(children, this.parserOptions) }
            </Link>
        );
    }

    replaceImages({ attribs }) {
        const attributes = attributesToProps(attribs);

        if (attribs.src) {
            return <Image { ...attributes } />;
        }
    }

    replaceWidget({ attribs }) {
        return <WidgetFactory { ...this.attributesToProps(attribs) } />;
    }

    render() {
        const { content } = this.props;
        return parser(content, this.parserOptions);
    }
}
```

{% endcode %}

Instead of rendering simple `<a>` anchor elements, the `Html` component replaces them with `<Link>` – making them work with the `react-router`. Instead of outputting `<img>` elements, it converts them into `<Image>` components, making them consistent with the rest of the app.

Importantly, all `<widget>` elements are turned into the `<WidgetFactory>` component. This is what enables Scandi to process the CMS widgets.

### The `WidgetFactory` Component

In the `WidgetFactory`, different widget types are mapped to corresponding components. The `renderContent` function then renders the appropriate component for the widget, passing on all the attributes as props:

```jsx
import PropTypes from 'prop-types';
import { lazy, PureComponent, Suspense } from 'react';

import RenderWhenVisible from 'Component/RenderWhenVisible';

import {
    CATALOG_PRODUCT_LIST,
    NEW_PRODUCTS,
    RECENTLY_VIEWED,
    SLIDER
} from './WidgetFactory.config';

import './WidgetFactory.style';

export const ProductListWidget = lazy(() => import(/* webpackMode: "lazy", webpackChunkName: "category" */ 'Component/ProductListWidget'));
export const NewProducts = lazy(() => import(/* webpackMode: "lazy", webpackChunkName: "category" */ 'Component/NewProducts'));
export const HomeSlider = lazy(() => import(/* webpackMode: "lazy", webpackChunkName: "cms" */ 'Component/SliderWidget'));
export const RecentlyViewedWidget = lazy(() => import(/* webpackMode: "lazy", webpackChunkName: "category" */ 'Component/RecentlyViewedWidget'));

/** @namespace Component/WidgetFactory/Component */
export class WidgetFactory extends PureComponent {
    static propTypes = {
        type: PropTypes.string.isRequired
    };

    renderMap = {
        [SLIDER]: {
            component: HomeSlider,
        },
        [NEW_PRODUCTS]: {
            component: NewProducts
        },
        [CATALOG_PRODUCT_LIST]: {
            component: ProductListWidget
        },
        [RECENTLY_VIEWED]: {
            component: RecentlyViewedWidget
        }
    };

    renderContent() {
        const { type } = this.props;
        const {
            component: Widget
        } = this.renderMap[type] || {};

        if (Widget !== undefined) {
            return (
                <RenderWhenVisible>
                    <Widget { ...this.props } />
                </RenderWhenVisible>
            );
        }

        return null;
    }

    render() {
        return (
            <Suspense fallback={ this.renderFallback() }>
                { this.renderContent() }
            </Suspense>
        );
    }
}

export default WidgetFactory;
```

Finally, the individual widget components (`ProductListWidget`, `NewProducts`, etc.) are regular React components responsible for rendering their widget.


# Creating a Magento Widget

Since widgets are created in the admin panel, we first need to write some Magento logic for the backend part of the widget.

The Magento Developer Docs have a [great guide](https://devdocs.magento.com/guides/v2.4/ext-best-practices/tutorials/custom-widget.html) for creating a widget. Lets walk through what you need to do to create a new widget to later make it work with Scandi.

## Create the Widget Block

Create a Block class for the widget. It needs to extend `Template` and implement the `BlockInterface`. Other than that, the only thing we need to do is specify the `_template` the widget uses.

{% code title="app/code/ScandiTutorials/CustomWidget/Block/Widget/NewsletterWidget.php" %}

```php
<?php declare(strict_types=1);

namespace ScandiTutorials\\CustomWidget\\Block\\Widget;

use Magento\\Framework\\View\\Element\\Template;
use Magento\\Widget\\Block\\BlockInterface;

class NewsletterWidget extends Template implements BlockInterface
{
    protected $_template = "widget/newsletter_widget.phtml";
}
```

{% endcode %}

But we haven't created the template yet! So let's do that next. If you were writing the template for Magento, you would need to render all the elements needed for the widget.

However, since all the Scandi rendering logic happens on the frontend using React, the template can be much simpler. The template merely needs to render a single element to specify the widget type.

This is also where we "send" all the widget parameters to the frontend. Let's say our widget will have a single String parameter, the title. We can get that parameter with `$block->escapeHtml($block->getData('title'))` and assign it to the `data-title` attribute:

{% code title="app/code/ScandiTutorials/CustomWidget/view/frontend/templates/widget/newsletter\_widget.phtml" %}

```php
<?php
/** ScandiTutorials\\CustomWidget\\Block\\Widget\\NewsletterWidget $block */
?>
<widget type="newsletter" data-title="<?= $block->escapeHtml($block->getData('title')) ?>"></widget>
```

{% endcode %}

You may realize that `widget` is not a real element type, and both attributes are custom too. This is ok, because all the HTML will get parsed on the frontend to render a custom React element.

For now, we haven't implemented any rendering on the frontend yet. So if you create a test page with this widget and view it, you won't see it. However, if you check the CMS content response received from the server, you'll see that the widget is there!

```javascript
{
  "data": {
    "cmsPage": {
      "title": "test",
      "content": "<widget type=\\"newsletter\\" data-title=\\"hello world\\"></widget>\\n",
      "page_width": "default",
      "content_heading": "",
      "meta_title": "",
      "meta_description": "",
      "meta_keywords": ""
    }
  }
}
```


# Implementing the Rendering

Define how your widget appears in the Scandi theme

Now that a `<widget>` element can appear in the CMS content, we need to implement parsing it and rendering a custom React element in its place. Luckily, as outlined in the intro section, Scandi already parses all CMS content – all we need to do is define a new widget type!

## Create a Component

We need to create a Component for the widget. This will just be a regular Scandi React component that takes the widget attributes (only `data-title` in this case) as props.

To create a new widget, you can just use the ScandiPWA CLI utility:

```
scandipwa create component NewsletterWidget
```

Our widget needs to let the user subscribe to the newsletter. Thankfully, this functionality is already implemented in the `NewsletterSubscription` component, so we can re-use that. All we need to do is add the widget title. Hence, the implementation is pretty simple:

{% code title="src/component/NewsletterWidget/NewsletterWidget.component.js" %}

```jsx
/** @namespace TutorialCsaApp/Component/NewsletterWidget/Component/NewsletterWidgetComponent */
export class NewsletterWidgetComponent extends PureComponent {
    static propTypes = {
        'data-title': propTypes.string.isRequired
    };

    render() {
        const { 'data-title': title } = this.props;

        return (
            <div block="NewsletterWidget">
                <h2>{ title }</h2>
                <NewsletterSubscription />
            </div>
        );
    }
}
```

{% endcode %}

Now we have a component that can render out the Newsletter widget. However, it is not integrated into the Scandi widget system yet. Let's do that next!

## Configure the Newsletter Widget

As mentioned in the intro, the `WidgetFactory` is responsible for determining the widget type and rendering an appropriate component. This mapping is configured in the `renderMap`:

{% code title="node\_modules/@scandipwa/scandipwa/src/component/WidgetFactory/WidgetFactory.component.js" %}

```javascript
    renderMap = {
        [SLIDER]: {
            component: HomeSlider,
            fallback: this.renderSliderFallback
        },
        [NEW_PRODUCTS]: {
            component: NewProducts
        },
        [CATALOG_PRODUCT_LIST]: {
            component: ProductListWidget
        },
        [RECENTLY_VIEWED]: {
            component: RecentlyViewedWidget
        }
    };
```

{% endcode %}

Note that each key is a string constant that corresponds to the widget type. This means that slider-type widgets will use the HomeSlider component, for example.

We want to add our own configuration entry for the newsletter widget. To do this, let's override the WidgetFactory component:

```
scandipwa override component WidgetFactory
```

Only select the WidgetFactory class to override it, and leave the other options blank - we don't need to override anything else!

```
? Choose things to extend in WidgetFactory.component.js WidgetFactory
? What would you like to do with styles? Keep
? Choose things to extend in WidgetFactory.config.js 

NOTE!

     The following files have been created:
     src/component/WidgetFactory/WidgetFactory.component.js
```

Now, we can update the configuration:

{% code title="src/component/WidgetFactory/WidgetFactory.component.js" %}

```jsx
import NewsletterWidget from 'Component/NewsletterWidget';
import {
    HomeSlider,
    NewProducts,
    ProductListWidget,
    RecentlyViewedWidget,
    WidgetFactory as SourceWidgetFactory
} from 'SourceComponent/WidgetFactory/WidgetFactory.component';

import {
    CATALOG_PRODUCT_LIST,
    NEW_PRODUCTS,
    RECENTLY_VIEWED,
    SLIDER
} from './WidgetFactory.config';

export {
    ProductListWidget,
    NewProducts,
    HomeSlider,
    RecentlyViewedWidget
};

/** @namespace TutorialCsaApp/Component/WidgetFactory/Component/WidgetFactoryComponent */
export class WidgetFactoryComponent extends SourceWidgetFactory {
    renderMap = {
        [SLIDER]: {
            component: HomeSlider,
            fallback: this.renderSliderFallback
        },
        [NEW_PRODUCTS]: {
            component: NewProducts
        },
        [CATALOG_PRODUCT_LIST]: {
            component: ProductListWidget
        },
        [RECENTLY_VIEWED]: {
            component: RecentlyViewedWidget
        },
        newsletter: { // note: "newsletter" is the widget type
            component: NewsletterWidget
        }
    };
}

export default WidgetFactoryComponent;
```

{% endcode %}

Now, if you test out your widget, you should see that it shows up as a title+subscription box! You can now use it anywhere widgets are supported.

![](/files/-Mi5ulcPO50O7gYps1l8)


# Video Tutorials

Watch our tutorials and follow along to get a taste of Scandi!


# #1 Setting up and talking theory

{% embed url="<https://youtu.be/ON37CsjAANs>" %}
Let's talk theory
{% endembed %}

ScandiPWA v3 has arrived at your doorstep and with it comes a new tech stack. Let’s talk about it!

Topics covered in this tutorial:

* ​[What’s Wrong With jQuery?](/tutorials/video-tutorials/lets-talk-theory#whats-wrong-with-jquery)​
* ​[History API](/tutorials/video-tutorials/lets-talk-theory#history-api)​
* ​[React and JSX](/tutorials/video-tutorials/lets-talk-theory#react-and-jsx)​
* ​[Setting Up The Environment](/tutorials/video-tutorials/lets-talk-theory#setting-up-the-environment)​
  * ​[VSCode Extensions](/tutorials/video-tutorials/lets-talk-theory#vscode-extensions)​
  * ​[Node](/tutorials/video-tutorials/lets-talk-theory#node)​
  * ​[Yarn](/tutorials/video-tutorials/lets-talk-theory#yarn)​
* ​[Installing The ScandiPWA App](/tutorials/video-tutorials/lets-talk-theory#installing-the-scandipwa-app)​

## What’s Wrong With jQuery? <a href="#whats-wrong-with-jquery" id="whats-wrong-with-jquery"></a>

ScandiPWA v2 used [jQuery](https://jquery.com/) - an API for working with document object models or DOMs. Allegedly, jQuery lets you do more while writing less. So, why use something else?

Each page of an application has a header, a footer and some content. We need to customize all of this periodically. The jQuery library theoretically allows us to have easy access to any specific element, but it would be very messy to implement as a jQuery template due to the fact that it consists of pure strings that are hard to edit.

## History API <a href="#history-api" id="history-api"></a>

With the coming-of-age of [history API](https://developer.mozilla.org/en-US/docs/Web/API/History_API) ScandiPWA has switched to React.

Previously each page you visited made a request to a server, which then provided a renderable output. Now, we can skip the server step and start to render the page on the client’s side by re-using the URL that’s currently being visited, if it had been visited at some point in history.

The history API lets us track the changes made in the state of the application and control the routing.

## React and JSX <a href="#react-and-jsx" id="react-and-jsx"></a>

Moreover, [React](https://reactjs.org/) in itself allows us to write simple, editable templates and components. These so-called components are encapsulated code chunks that later will be shipped or not shipped based on necessity.

​[JSX](https://reactjs.org/docs/introducing-jsx.html) is a syntax extension to JavaScript which means that we can use the JS variables, expressions etc. inside of it to write a template.

For example, here we define a constant that gets assigned a JSX tag:

```
const element = <h1>Hello, world!</h1>;
```

The following example can be found [here](https://reactjs.org/docs/introducing-jsx.html) and it shows us how we can declare a variable, use it in a template and immediately render it:

```
const name = 'Josh Perez';const element = <h1>Hello, {name}</h1>;​ReactDOM.render(  element,  document.getElementById('root'));
```

Let’s briefly look at the [components](https://reactjs.org/docs/components-and-props.html).

There are two types of components:

* functional components - processes properties and returns a template
* class components - let’s you work with OOP concepts
  * very useful when you’re working with a theme that should be extended (like ScandiPWA)

## Setting Up The Environment <a href="#setting-up-the-environment" id="setting-up-the-environment"></a>

The ScandiPWA team recommends using VSCode as a development environment due to its ease of use and extension availability.

### VSCode Extensions <a href="#vscode-extensions" id="vscode-extensions"></a>

Recommended VSCode extensions:

* ​[ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) - JavaScript code quality assurance tool. ESlint analyzes your code and warns you of any errors that could compromise its stability.
* ScandiPWA Development Toolkit - lets you customize your theme with more ease, you can download the `.vsix` file [here](https://drive.google.com/file/d/1Xm_sWSh4ceFC70Gc88VvRy23w5F6C6Zd/view).

You can manually install a VSCode extension by going to the extensions view or hitting `Ctrl + Shift + X` on the keyboard. Then click on `Views And More Actions...` and `Install From VSIX`.

Alternatively, you can also use the command line:

```
code --install-extension path/to/myextension.vsix
```

### Node <a href="#node" id="node"></a>

Check if [Node.js](https://nodejs.org/en/) is installed on your computer by typing the following command in the terminal:Copy

```
node -v
```

For the development of ScandiPWA you need Node version 10 and higher.

### Yarn <a href="#yarn" id="yarn"></a>

Optional in order to get faster install times than using `npm`. If you don’t want to use Yarn, `npm` will suffice.

## Installing The ScandiPWA App <a href="#installing-the-scandipwa-app" id="installing-the-scandipwa-app"></a>

After you have Node and Yarn installed you can type the following in your command line:

```
npx create-scandipwa-app <your-app-name>
```

After you’ve successfully created the ScandiPWA theme, go to your theme’s folder and run the following command that will compile the project:

```
yarn start
```

Let’s look at the file structure:

```
📂<your-app-name> ┣ 📂i18n           # internationalization folder ┣ 📂magento ┣ 📂node_modules   # contains the app's dependencies ┣ 📂public         # assets should be put here ┣ 📂src            # empty source folder ┣ 📜composer.json ┣ 📜package.json ┗ 📜yarn.lock
```

The `src` or source folder will be empty in a fresh install. Since ScandiPWA is meant to be extended, not modified, a fresh install will run the default ScandiPWA theme until new files that will override the theme are added to the `src` folder.

You can check out the what the default theme consists of in `node_modules/@scandipwa/scandipwa/src` - the original source folder.

The `public` folder should contain any assets you want to have like fonts, icons etc.

`i18n` or internationalization folder contains files for locale handling and translations of any phrases you have on the app.

The `package.json` file contains two dependencies:

```
"dependencies": {    "@scandipwa/scandipwa": "0.0.1",    "@scandipwa/scandipwa-scripts": "0.0.5"}
```

`scandipwa` dependency currently is in v2.17.0. `scandipwa-scripts` dependency contains the `webpack` configuration.

There are two scripts available out of the box as well:

```
"scripts": {    "start": "scandipwa-scripts start",    "build": "scandipwa-scripts build"}
```

The `start` command begins the local file watching process and starts the local dev server that will let you see the changes in the browser right as you’ve made them.

Note that `yarn start` was the first command that we ran, thus the development process can begin. You can check out your theme by going to `localhost:3000` in your browser.

The `build` command is for starting the production build process. You can build in Magento mode as a Magento theme and you can also build as a store front.

If you want to build ScandiPWA as a Magento theme, you’ll notice that the `composer.json` file contains the theme registration and the `magento` folder contains all of the assets needed for defining a theme.


# #2 Templating in React

{% embed url="<https://youtu.be/0cdrcAbzlr0>" %}
Going hand on
{% endembed %}

In this tutorial we will discuss the following React concepts:

* [Component state](/tutorials/video-tutorials/templating-in-react#component-state)
* [Components update tracking logic](/tutorials/video-tutorials/templating-in-react#components-update-tracking-logic)
* [ShouldUpdate method](/tutorials/video-tutorials/templating-in-react#shouldupdate-method)
* [PureComponents VS traditional Components](/tutorials/video-tutorials/templating-in-react#purecomponents-vs-traditional-components)
* [Handling side-effects in getDerivedStateFromProps](/tutorials/video-tutorials/templating-in-react#handling-side-effects-in-getderivedstatefromprops)

You should be able to discuss the following topics after watching this video:

* onClick as an arrow function, bind, non-bind
* setState as a function and via state destruct
* Checking for prev value in componentDidUpdate
* Defining state examples, in constructor, as a property
* Updating state in component did-update consequences
* Keeping previous property in state

This tutorial builds on the [previous](/tutorials/video-tutorials/lets-talk-theory) one. If you’ve been wanting to work with ScandiPWA you’ll know that it’s meant to be extended. This is done by creating new files in your project that will override the defaults.

Run `yarn start` to start the development set-up and let’s start by overriding the `index.js` file. Create a new `index.js` file in your `src` folder. The application should compile automatically after you’ve saved any changes.

This is what our `src/index.js` should contain:

```javascript
import { Component } from 'react';
import ReactDOM from 'react-dom';

class Button extends Component {
    render () {
        return (
            <button>Click me!</button>
        );
    }
}

ReactDOM.render(
    // this is the component's template
    <Button />,                 
    document.getElementById('root')
);
```

The component should be passed as a render template and after this we should tell React where we expect this element to render.

If you go to `localhost:3000` in your browser you’ll see that the element has rendered.

Let’s make it a clickable button:

```javascript
import { Component } from 'react';
import ReactDOM from 'react-dom';

class Button extends Component {
    onButtonClick() {
        // writes logs in console
        console.log('you clicked me!');         
    }

    render () {
        return (
            // adds event listener
            <button onClick={ this.onButtonClick }>Click me!</button>
        );
    }
}

ReactDOM.render(
    <Button />,               
    document.getElementById('root')
);
```

In React you need to provide a listener for the element when it’s initially rendered, with the listener in this case being `<button onClick={ this.onButtonClick }>`. You can read more about handling events in React [here](https://reactjs.org/docs/handling-events.html).

## Component state

Let’s set the default state and change it dynamically. The following code should start counting the clicks from zero:

```javascript
import { Component } from 'react';
import ReactDOM from 'react-dom';

class Button extends Component {
    state = {
        // sets the default state
        clickCount: 0     
    };

    onButtonClick = () => {
        // imports the default state
        const { clickCount } = this.state;
        // updates the new state 
        this.setState({ clickCount: clickCount + 1 })
        console.log('you clicked me!');
    };

    render () {
        return (
            <button onClick={ this.onButtonClick }>Click me!</button>
        );
    }
}

ReactDOM.render(
    <Button />,               
    document.getElementById('root')
);
```

We need to transform the simple method `onButtonClick()` to a function property in order to not get a TypeError: Cannot read property ‘state’ of undefined. This can be done by adding an arrow function to `onButtonClick = () =>`. You can read about passing fuctions to components in React [here](https://reactjs.org/docs/faq-functions.html).

## Components update tracking logic

Let’s see how we can update the component with a simple tracking feature:

```javascript
import { Component } from 'react';
import ReactDOM from 'react-dom';

class Button extends Component {
    state = {
        clickCount: 0     
    };

    onButtonClick = () => {
        const { clickCount } = this.state;
        this.setState({ clickCount: clickCount + 1 });
    };

    render () {
        // imports the latest click count from state
        const { clickCount } = this.state;   

        return (
            <div>
                <span>
                    You clicked me
                    <b>{ clickCount }</b>
                </span>
                <button onClick={ this.onButtonClick }>Click me!</button>
            </div>
        );
    }
}

ReactDOM.render(
    <Button />,            
    document.getElementById('root')
);
```

{% hint style="warning" %}
JSX expects only one root element
{% endhint %}

Since JSX expects only one root element to be present, we encase the `span` and `button` tags with `div`.

An alternative way to set the state would be:

```javascript
    onButtonClick = () => {
        // const { clickCount } = this.state;
        // this.setState({ clickCount: clickCount + 1 });

        this.setState(({ clickCount }) => ({ clickCount: clickCount + 1 }));

    };
```

`const { clickCount } = this.state;` lets us further on just use `{ clickCount }` to refer to `this.state.clickCount`. These are called state hooks and you can read more about them [here](https://reactjs.org/docs/hooks-state.html).

```javascript
import { Component } from 'react';
import ReactDOM from 'react-dom';

class Button extends Component {
    // proper state declaration
    //    state = {
    //        clickCount: 0     
    //    };
    
    // alternative state declaration
    constructor (props){
        super(props);

        this.state = {
            clickCount: 0
        };
    }

    onButtonClick = () => {
        const { clickCount } = this.state;
        this.setState({ clickCount: clickCount + 1 });
    };

    render () {
        // imports the latest click count from state
        const { clickCount } = this.state;   

        return (
            <div>
                <span>
                    You clicked me
                    <b>{ clickCount }</b>
                </span>
                <button onClick={ this.onButtonClick }>Click me!</button>
            </div>
        );
    }
}

ReactDOM.render(
    <Button />,            
    document.getElementById('root')
);
```

An alternative way to set the default state would be the following, which is very similar to PHP:

```javascript
class Button extends Component {
    constructor (props){
        // reference to a parent class that we extend
        super(props);

        this.state = {
            clickCount: 0
        };
    }
```

Let’s consider the component’s life cycle. We’ve created a state, but how can we trace what the component is/was doing?

First, let’s name our division `<div id="abc">`, add some console logs and find out when they’ll get triggered:

```javascript
import { Component } from 'react';
import ReactDOM from 'react-dom';

class Button extends Component {
    constructor (props){
        super(props);

        this.state = {
            clickCount: 0
        };

        // make any request here, besides DOM manipulation
        console.log('constructor', document.getElementById('abc'));
    }

    componentDidMount() {
        // implement any DOM manipulation
        console.log('mount', document.getElementById('abc'));
    }

    onButtonClick = () => {
        const { clickCount } = this.state;
        this.setState({ clickCount: clickCount + 1 });
    };

    render () {
        const { clickCount } = this.state;   

        return (
            <div id="abc">
                <span>
                    You clicked me
                    <b>{ clickCount }</b>
                </span>
                <button onClick={ this.onButtonClick }>Click me!</button>
            </div>
        );
    }
}

ReactDOM.render(
    <Button />,            
    document.getElementById('root')
);
```

When we try this out in our browser we can see in the Console that the `constructor` returns `null` and `mount` returns `<div id="abc"></div>`.

This shows us that the component’s constructor is called before it is mounted. You can read more about the constructor [here](https://reactjs.org/docs/react-component.html#constructor) and componentDidMount [here](https://reactjs.org/docs/react-component.html#componentdidmount).

If you want to add a CSS variable or have access to a DOM node, you can do it from her&#x65;**:**

```javascript
    componentDidMount() {
        // implement any DOM manipulation
        console.log('mount', document.getElementById('abc'));
    }
```

Next, we have the [componentDidUpdate()](https://reactjs.org/docs/react-component.html#componentdidupdate) method which allows us to see if something was updated in the component:

```javascript
    componentDidMount() {
        // implement any DOM manipulation
        console.log('mount', document.getElementById('abc'));
    }

    componentDidUpdate() {
        // triggered by state & props change
        console.log('update');
    }
```

After adding the update log you should be able to see `update` in the Console any time the button had been clicked.

Let’s add a new class. If ESlint is showing you a ‘max classes per file’ error, disable it for the sake of this tutorial. Note that when developing an actual project, try to stick to the one component per file rule.

You can disable ESlint rule warnings for the entire file by adding the `/* eslint-disable */` at the top of it.

```javascript
/* eslint-disable max-classes-per-file, @scandipwa/scandipwa-guidelines/only-one-class */

import { Component } from 'react';
import ReactDOM from 'react-dom';

class Button extends Component {
    constructor (props){
        super(props);

        this.state = {
            clickCount: 0
        };

        console.log('constructor', document.getElementById('abc'));
    }

    componentDidMount() {
        console.log('mount', document.getElementById('abc'));
    }

    componentDidUpdate() {
        // triggered by state & props change
        console.log('update');
    }   

    onButtonClick = () => {
        const { clickCount } = this.state;
        this.setState({ clickCount: clickCount + 1 });
    };

    render () {
        const { clickCount } = this.state;   

        return (
            <div id="abc">
                <span>
                    You clicked me
                    <b>{ clickCount }</b>
                </span>
                <button onClick={ this.onButtonClick }>Click me!</button>
            </div>
        );
    }
}

// the new class
class Wrapper extends Component  {
    // defines the state
    state = {
        clickCount: 0     
    };

    onButtonClick = () => {
        const { clickCount } = this.state;
        this.setState({ clickCount: clickCount + 1 });
    };

    render() {
        return(
            <div>
                {/* calls the button component */}
                <Button />
                {/* button with event listener */}
                <button onClick={ this.onButtonClick }>Update wrapper</button>
            </div>
        );
    }
}

ReactDOM.render(
    // renders the wrapper component instead
    <Wrapper />,            
    document.getElementById('root')
);
```

Let’s go to our browser and try out the changes. So, if you click on the 1st button, the update will get triggered in Console, but if you click the 2nd button, the update will also get triggered. Why is that? This happens because any updates in the top component will update the bottom component in a very inefficient way.

## ShouldUpdate method

Instead we should put [`shouldComponentUpdate()`](https://reactjs.org/docs/react-component.html#shouldcomponentupdate) in `Button`:

```javascript
class Button extends Component {
    constructor (props){
        super(props);

        this.state = {
            clickCount: 0
        };

        console.log('constructor', document.getElementById('abc'));
    }

    componentDidMount() {
        console.log('mount', document.getElementById('abc'));
    }

    shouldComponentUpdate(nextProps, nextState) {
        const { clickCount: nextClickCount } = nextState;
        const { clickCount } = this.state;

        // updates only if state's click count changes
        if (clickCount !== nextClickCount) {
            return true;
        }

        return false;
    }     

    componentDidUpdate() {
        // triggered by state & props change
        console.log('update');
    }

    onButtonClick = () => {
        const { clickCount } = this.state;
        this.setState({ clickCount: clickCount + 1 });
    };

    render () {
        const { clickCount } = this.state;   

        return (
            <div id="abc">
                <span>
                    You clicked me
                    <b>{ clickCount }</b>
                </span>
                <button onClick={ this.onButtonClick }>Click me!</button>
            </div>
        );
    }
}
```

Going back to the browser we can see that the clicks in `Button` will trigger updates, but clicks in `Wrapper` will not. The `shouldComponentUpdate()` method might get tedious if you’re working with multiple components. This is where `PureComponents` come in.

## PureComponents VS traditional Components

PureComponent performs a shallow comparison of the props and state any time props or state changes. PureComponent essentially is Component with built in `shouldComponentUpdate` method.Copy

```javascript
/* eslint-disable max-classes-per-file, @scandipwa/scandipwa-guidelines/only-one-class */

import { Component, PureComponent } from 'react';
import ReactDOM from 'react-dom';

class Button extends PureComponent {
    constructor (props){
        super(props);

        this.state = {
            clickCount: 0
        };

        console.log('constructor', document.getElementById('abc'));
    }

    componentDidMount() {
        console.log('mount', document.getElementById('abc'));
    }

    componentDidUpdate() {
        // triggered by state & props change
        console.log('update');
    }

    onButtonClick = () => {
        const { clickCount } = this.state;
        this.setState({ clickCount: clickCount + 1 });
    };

    render () {
        const { clickCount } = this.state;   

        return (
            <div id="abc">
                <span>
                    You clicked me
                    <b>{ clickCount }</b>
                </span>
                <button onClick={ this.onButtonClick }>Click me!</button>
            </div>
        );
    }
}

class Wrapper extends Component  {
    // defines the state
    state = {
        clickCount: 0     
    };

    onButtonClick = () => {
        const { clickCount } = this.state;
        this.setState({ clickCount: clickCount + 1 });
    };

    render() {
        return(
            <div>
                <Button />
                <button onClick={ this.onButtonClick }>Update wrapper</button>
            </div>
        );
    }
}

ReactDOM.render(
    // renders the wrapper component instead
    <Wrapper />,            
    document.getElementById('root')
);
```

This should act the same way as the previous Component with `shouldComponentUpdate` method. This is why it is preferred to use PureComponent by default.

{% hint style="warning" %}
Method:`shouldComponentUpdate` only works if you’re extending Component, not PureComponent.
{% endhint %}

Let’s change up the wrapper class and add the `wrapperCount= { clickCount }` property to `Button`.

```javascript
class Wrapper extends Component  {
    // defines the state
    state = {
        clickCount: 0     
    };

    onButtonClick = () => {
        const { clickCount } = this.state;
        this.setState({ clickCount: clickCount + 1 });
    };

    render() {
        const { clickCount } = this.state;

        return(
            <div>
                {/* wrapperCount prop passed to Button component */}
                <Button wrapperCount= { clickCount } />
                <button onClick={ this.onButtonClick }>Update wrapper</button>
            </div>
        );
    }
}
```

Now, when trying this out in the browser, the update should also get triggered when clicking the ‘Update wrapper’ button.

Let’s go back to the Button class. In order to change up the `componentDidUpdate` method we need to either set the required prop or set the default value.

```javascript
/* eslint-disable max-classes-per-file, @scandipwa/scandipwa-guidelines/only-one-class */

import { Component, PureComponent } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';

class Button extends PureComponent {
    // use either propTypes or defaultProps
    static propTypes = {
        // sets the required prop
        wrapperCount: PropTypes.number
    };

    static defaultProps = {
        // sets the default value
        wrapperCount: 0
    };

    constructor (props){
        super(props);

        this.state = {
            clickCount: 0
        };

        console.log('constructor', document.getElementById('abc'));
    }

    componentDidMount() {
        console.log('mount', document.getElementById('abc'));
    }

    componentDidUpdate(prevProps) {
        // triggered by state & props change

        const { wrapperCount } = this.props;
        const { wrapperCount: prevWrapperCount } = prevProps;

        if (wrapperCount !== prevWrapperCount) {
            // console.log('update');
            this.setState({ clickCount: wrapperCount })
        }
    }

    onButtonClick = () => {
        const { clickCount } = this.state;
        this.setState({ clickCount: clickCount + 1 });
    };
...
```

This is where ESLint would notify you that you shouldn’t use setState in `componentDidUpdate`. This is because setting the state here could lead to an infinite loop if the checkpoint before doing so would not be specific enough.

Read more about `componentDidUpdate` and infinite loops [here](https://reactjs.org/docs/react-component.html#componentdidupdate).

## Handling side-effects in getDerivedStateFromProps

We can bypass the `componentDidUpdate` issue by using `getDerivedStateFromProps`. Note that this method can’t access any values from the component.

```javascript
class Button extends PureComponent {
    // use either propTypes or defaultProps
    static propTypes = {
        // sets the required prop
        wrapperCount: PropTypes.number
    };

    static defaultProps = {
        // sets the default value
        wrapperCount: 0
    };

    constructor (props){
        super(props);

        this.state = {
            clickCount: 0,
            // set a new default
            prevWrapperCount: 0
        };


    static getDerivedStateFromProps(props, state) {
        // no access to current value is present
        // no access to `this`

        const { wrapperCount } = props;
        const { prevWrapperCount } = state;
        // you need to keep previous value in state

        // if wrapper count is not equal to previous value
        if (wrapperCount !== prevWrapperCount) {
            return {
                // update click count to wrapper count
                clickCount: wrapperCount,
                // update previous value
                prevWrapperCount: wrapperCount
            };
        }

        return null;
    }

        componentDidMount() {
        console.log('mount', document.getElementById('abc'));
    }
...
```

You should put `getDerivedStateFromProps` before `componentDidMount`. This should work as previously with `componentDidUpdate`, except there is no possibility to enter an infinite loop.


# #3 Overriding a file

{% embed url="<https://youtu.be/Oi45D1r1DUs>" %}
How to override a file
{% endembed %}

In this tutorial we will showcase and talk about following topics:

* [Themes and parent themes](https://docs.create-scandipwa-app.com/themes/extensions-and-themes) (link to Create ScandiPWA App documentation)
* [File overrides](https://docs.create-scandipwa-app.com/themes/parent-themes) (link to Create ScandiPWA App documentation)

As well as from this documentation:

{% content-ref url="/pages/-MNcP\_Mzb\_fzWwn4SQ1y" %}
[Overriding JavaScript](/developing-with-scandi/override-mechanism/extending-javascript)
{% endcontent-ref %}

{% content-ref url="/pages/-MNcRT69Hjndi55WsIsz" %}
[Overriding Styles](/developing-with-scandi/override-mechanism/extending-styles)
{% endcontent-ref %}

{% content-ref url="/pages/-MOjieryc8iY8bkRdoj8" %}
[Overriding the HTML / PHP](/developing-with-scandi/override-mechanism/overriding-the-index-file)
{% endcontent-ref %}


# #4 Styling the application

{% embed url="<https://youtu.be/W7BiWI4yVsc>" %}
Style it (tutorial 3)
{% endembed %}

Topics covered in this tutorial:

* [Adding style files (.css)](/tutorials/video-tutorials/styling-the-application#adding-style-files-css)
* [Using CSS properties without a prefix](/tutorials/video-tutorials/styling-the-application#using-css-properties-without-a-prefix)
* [Using CSS variables](/tutorials/video-tutorials/styling-the-application#using-css-variables)
* [Switching to SCSS to build classnames](/tutorials/video-tutorials/styling-the-application#switching-to-scss-to-build-classnames)
* [Organizing classnames with BEM](/tutorials/video-tutorials/styling-the-application#organizing-classnames-with-bem)

After watching this video, you should be able to discuss the following topics:

* Gluing classnames with “&”
* Mods with boolean and non-boolean modifiers
* Using mix prop to combine classes
* Root and non-root declarations of CSS custom properties

## Adding style files (.css)

Add a new style file `style.css` in your theme’s `src` folder:

```
📂<your-app-name>
 ┣ 📂i18n          
 ┣ 📂magento
 ┣ 📂node_modules   
 ┣ 📂public         
 ┣ 📂src 
 ┃ ┣ 📜index.js
 ┃ ┗ 📜style.css      # new file
 ┣ 📜composer.json
 ┣ 📜package.json
 ┗ 📜yarn.lock
```

After adding `style.css` you need to import it in `index.js` by adding:

```javascript
import './style.css';
```

This tutorial builds on the previous one. If you haven’t completed it, you can just copy the following contents and add them to `src/index.js`.

```javascript
/* eslint-disable max-classes-per-file, @scandipwa/scandipwa-guidelines/only-one-class */

import { Component, PureComponent } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';

// new style import
import './style.css';

class Button extends PureComponent {
    // use either propTypes or defaultProps
    static propTypes = {
        // sets the required prop
        wrapperCount: PropTypes.number
    };

    static defaultProps = {
        // sets the default value
        wrapperCount: 0
    };

    constructor (props){
        super(props);

        this.state = {
            clickCount: 0,
            // set a new default
            prevWrapperCount: 0
        };


    static getDerivedStateFromProps(props, state) {
        // no access to current value is present
        // no access to `this`

        const { wrapperCount } = props;
        const { prevWrapperCount } = state;
        // you need to keep previous value in state

        // if wrapper count is not equal to previous value
        if (wrapperCount !== prevWrapperCount) {
            return{
                // update click count to wrapper count
                clickCount: wrapperCount,
                // update previous value
                prevWrapperCount: wrapperCount
            };
        }

        return null;
    }

    componentDidMount() {
        console.log('mount', document.getElementById('abc'));
    }
    componentDidUpdate() {
        // triggered by state & props change
        console.log('update');
    }

    onButtonClick = () => {
        const { clickCount } = this.state;
        this.setState({ clickCount: clickCount + 1 });
    }

    render () {
        const { clickCount } = this.state;   

        return (
            <div id="abc">
                <span>
                    You clicked me
                    <b>{ clickCount }</b>
                </span>
                <button onClick={ this.onButtonClick }>Click me!</button>
            </div>
        );
    }
}



class Wrapper extends Component  {
    // defines the state
    state = {
        clickCount: 0     
    };

    onButtonClick = () => {
        const { clickCount } = this.state;
        this.setState({ clickCount: clickCount + 1 });
    };

    render() {
        const { clickCount } = this.state;

        return(
            <div>
                <Button wrapperCount= { clickCount } />
                <button onClick={ this.onButtonClick }>Update wrapper</button>
            </div>
        );
    }
}

ReactDOM.render(
    // renders the wrapper component instead
    <Wrapper />,            
    document.getElementById('root')
);
```

## Using CSS properties without a prefix

It is recommended to stick to styling by classes, as it’ll make your life easier in the long run:

```javascript
// this is className="heading" in index.js
.heading {
    font-size: 20px;
    font-family: monospace;
}

// this is className="button" in index.js
.button {
    appearance: none;
    border: 1px solid black;
    padding: .25rem 1rem;
    background-color: hotpink;
}
```

A REM unit is equal to computed value of font-size for the root element. So, if your font default is 12px, 1 REM unit will be 12px as well and 0,25 REM units will be 3px.

In order to make the styles work, we need to add class names to the render methods found in `index.js`.

First, let’s edit the `class Button`:

```javascript
    render () {
        const { clickCount } = this.state;   

        return (
            <div id="abc">
                {/* change span to h1 and add classNames */}
                <h1 className="heading">
                    You clicked me
                    <b>{ clickCount }</b>
                </h1>
                <button className="button" onClick={ this.onButtonClick }>Click me!</button>
            </div>
        );
    }
```

Next, the same for `class Wrapper`:

```javascript
    render() {
        const { clickCount } = this.state;

        return(
            <div>
                <Button wrapperCount= { clickCount } />
                <button className="button" onClick={ this.onButtonClick }>Update wrapper</button>
            </div>
        );
    }
```

For now, ESlint rules won’t let you use className, so for the sake of this tutorial, disable this rule for the whole file.

Now the style should change accordingly. If we use ‘Inspect element’ on a button in the browser, we should see in the Styles tab that a previously unknown property `webkit-appearance` has been added. This happened due to the fact that the compilator automatically adds vendor prefixes to the properties.

Here you can see browser specific examples:

```javascript
…
-webkit-flex: 101;  # Chrome and Safari
-moz-flex: 101;     # Mozilla
-o-flex: 101;       # Opera
-ms-flex: 101;      # Internet Explorer
…
```

## Using CSS variables

Instead of writing out the color references directly, we should use CSS variables or CSS custom properties. They are declared in the `root` element and later referenced using `var()`.

```javascript
:root {
    --button-color: hotpink;
    --button-border-color: black;
}

.heading {
    font-size: 20px;
    font-family: monospace;
}

.button {
    /** ... gets auto-prefixed */
    appearance: none;
    border: 1px solid var(--button-border-color);
    padding: .25rem 1rem;
    background-color: var(--button-color);
}
```

In order to showcase the differences in these approaches more clearly, we can define a new `className` in the `Button` component.

```javascript
    render () {
        const { clickCount } = this.state;   

        return (
            <div className="button-wrapper">
                <h1 className="heading">
                    You clicked me
                    <b>{ clickCount }</b>
                </h1>
                <button className="button" onClick={ this.onButtonClick }>Click me!</button>
            </div>
        );
    }
```

Now, let’s style the `button-wrapper`:

```javascript
:root {
    --button-color: hotpink;
    --button-border-color: black;
}

.heading {
    font-size: 20px;
    font-family: monospace;
}

.button {
    /** ... gets auto-prefixed */
    appearance: none;
    border: 1px solid var(--button-border-color);
    padding: .25rem 1rem;
    background-color: var(--button-color);
}

.button-wrapper {
    --button-color: green;
}
```

Now, the background color of the button in `<div className="button-wrapper">` should be green. This happens because a CSS custom property has inheritance - it takes the topmost parent and goes downwards, looking for re-definition of the variable. If the property finds a re-definition, it’ll use the one closest to it.

This comes in handy when changing hover colors:

```javascript
:root {
    --button-color: hotpink;
    --button-hover-color: pink;
    --button-border-color: black;
}

.heading {
    font-size: 20px;
    font-family: monospace;
}

.button {
    /** ... gets auto-prefixed */
    appearance: none;
    border: 1px solid var(--button-border-color);
    padding: .25rem 1rem;
    background-color: var(--button-color);
}

.button:hover {
    --button-color: var(--button-hover-color);
}

.button-wrapper {
    --button-color: green;
}
```

## Switching to SCSS to build classnames

In order to improve the readability of the style file we can switch to SCSS - rename `style.css` to `style.scss` and change the name of `import` in `index.js` as well.

```javascript
import './style.scss';
```

Now, instead of repeating `.button` over and over again, we can, for example, add the `:hover` pseudo-class by nesting the properties.

```javascript
:root {
    --button-color: hotpink;
    --button-hover-color: pink;
    --button-border-color: black;
}

.heading {
    font-size: 20px;
    font-family: monospace;
}

.button {
    /** ... gets auto-prefixed */
    appearance: none;
    border: 1px solid var(--button-border-color);
    padding: .25rem 1rem;
    background-color: var(--button-color);

    &:hover {
        --button-color: var(--button-hover-color);
    }

    &-wrapper {
        --button-color: green;
    }   
}
```

The `&` works like glue that allows us to place our `className` and any selector or different `className` together.

## Organizing classnames with BEM

We can use [BEM](http://getbem.com/introduction/) to further organize our style files.

```javascript
:root {
    --button-color: hotpink;
    --button-hover-color: pink;
    --button-border-color: black;
}

/**
BEM - Block, Element, Modifier

Block (block): Heading, Button, MyElement (.)
.MyElement { ... }

Element (elem): Heading-Strong, Button-Icon, MyElement-Key
.Heading-Strong { ... }
.MyElement {
    &-Key {
        ...
    }
}

Modifiers (mods): Heading_isLarge, Button_type_icon
.Heading {
    &_isLarge {
        ...
    }

    &_type {
        &_icon {
            ...
        }
    }
}
*/

.heading {
    font-size: 20px;
    font-family: monospace;
}

.button {
    /** ... gets auto-prefixed */
    appearance: none;
    border: 1px solid var(--button-border-color);
    padding: .25rem 1rem;
    background-color: var(--button-color);

    &:hover {
        --button-color: var(--button-hover-color);
    }

    &-wrapper {
        --button-color: green;
    }   
}
```

Notice that blocks are named using PascalCase - each word in a compound word is capitalized. A block’s declaration usually is preceded by a dot, for example, `.MyElement`.

An element always follows a block and is defined using a dash and PascalCase, for example, `.Heading-Strong` where `Heading` is the block and `-Strong` is the element.

If, we’re using SCSS, we can use glue or `&` for BEM elements as well, for example:

```javascript
.MyElement{
    &-Key {
        ...
    }
}
```

Modifiers are the state definitions of an element or a block, these are usually preceded by an underscore and named using camelCase. Note that you can use either one-part or two-part modifiers.

One-part modifier is a boolean modifier that should start with `is` and non-boolean modifiers should be split in two parts, where the first part defines the type of the modification and the second part defines a value.

The next example uses the gluing method to define `.Heading_isLarge`, `.Heading-Key_isHuge` and `.Heading_type_icon`:

```javascript
.Heading {
    &_isLarge {
        ...
    }

    &-Key {
        &_isHuge {
            ...
        }
    }

    &_type {
        &_icon {
            ...
        }
    }
}
```

Let’s rearrange our style file to follow the BEM standards:

```javascript
:root {
    --button-color: hotpink;
    --button-hover-color: pink;
    --button-border-color: black;
}

/**
BEM - Block, Element, Modifier

Block (block): Heading, Button, MyElement (.)
.MyElement { ... }

Element (elem): Heading-Strong, Button-Icon, MyElement-Key
.Heading-Strong { ... }
.MyElement {
    &-Key {
        ...
    }
}

Modifiers (mods): Heading_isLarge, Button_type_icon
.Heading {
    &_isLarge {
        ...
    }

    &_type {
        &_icon {
            ...
        }
    }
}
*/

.Heading {
    font-size: 20px;
    font-family: monospace;
}

.Button {
    /** ... gets auto-prefixed */
    appearance: none;
    border: 1px solid var(--button-border-color);
    padding: .25rem 1rem;
    background-color: var(--button-color);

    &:hover {
        --button-color: var(--button-hover-color);
    }

    &-Wrapper {
        --button-color: green;
    }   
}
```

We should apply the same rules for `className` in `index.js`. For the `Button` class:

```javascript
    render () {
        const { clickCount } = this.state;   

        return (
            <div className="Button-Wrapper">
                <h1 className="Heading">
                    You clicked me
                    <b>{ clickCount }</b>
                </h1>
                <button className="Button" onClick={ this.onButtonClick }>Click me!</button>
            </div>
        );
    }
```

And for the `Wrapper` class:

```javascript
    render() {
        const { clickCount } = this.state;

        return(
            <div>
                <Button wrapperCount= { clickCount } />
                <button className="Button" onClick={ this.onButtonClick }>Update wrapper</button>
            </div>
        );
    }
```

Since it’s not exactly convenient to concatenate strings, in order to have multiple elements or modifiers, we should use the BEM HTML helpers. Let’s also add a modifier:

```javascript
class Button extends PureComponent {

// some stuff

    render () {
        const { clickCount } = this.state;   

        return (
            <div 
                block="Button" 
                elem="Wrapper" 
                mods={ { isLarge: true } }
            >
                <h1 block="Heading">
                    You clicked me
                    <b>{ clickCount }</b>
                </h1>
                <button block="Button" onClick={ this.onButtonClick }>Click me!</button>
            </div>
        );
    }
}

class Wrapper extends Component  {

// some stuff

    render() {
        const { clickCount } = this.state;

        return(
            <div>
                <Button wrapperCount= { clickCount } />
                <button block="Button" onClick={ this.onButtonClick }>Update wrapper</button>
            </div>
        );
    }
}
```

Add the same modifier to the style file:

```javascript
:root {
    --button-color: hotpink;
    --button-hover-color: pink;
    --button-border-color: black;
}

.Heading {
    font-size: 20px;
    font-family: monospace;
}

.Button {
    /** ... gets auto-prefixed */
    appearance: none;
    border: 1px solid var(--button-border-color);
    padding: .25rem 1rem;
    background-color: var(--button-color);

    &:hover {
        --button-color: var(--button-hover-color);
    }

    &-Wrapper {
        --button-color: green;

        &_isLarge {
            --button-color: blue;
        }
    }   
}
```

If you’re using the ScandiPWA ESlint rules, you can re-enable the no classNames rule, since we’re using BEM props now.

If you want to write something that previously consisted of two classNames, you can still use BEM. This can be done with `mix`, for example:

```javascript
class Wrapper extends Component  {

// some stuff

    render() {
        const { clickCount } = this.state;

        return(
            <div>
                <Button wrapperCount= { clickCount } />
                <button 
                    block="Button" 
                    mix={ { block:'Hello', elem:'World' } }
                    onClick={ this.onButtonClick }
                >
                    Update wrapper
                </button>
            </div>
        );
    }
}
```

We can see what happens in CSS by inspecting the button:

```javascript
<button class="Button Hello-World">
```

If you want to add a third block, you can mix it in the first mix:

```javascript
class Wrapper extends Component  {

// some stuff

    render() {
        const { clickCount } = this.state;

        return(
            <div>
                <Button wrapperCount= { clickCount } />
                <button 
                    block="Button" 
                    mix={ { block:'Hello', elem:'World', mix: {block: 'Alfred'} } }
                    onClick={ this.onButtonClick }
                >
                    Update wrapper
                </button>
            </div>
        );
    }
}
```

And by inspection we’ll see three class names:

```javascript
<button class="Button Hello-World Alfred">
```


# #5 Patterns of ScandiPWA

{% embed url="<https://youtu.be/c62CDwvnutk>" %}
Learning the way (tutorial 4)
{% endembed %}

In this tutorial we will talk about the main component files in ScandiPWA:

* [.component](/tutorials/video-tutorials/patterns-of-scandipwa#component)
* [.config](/tutorials/video-tutorials/patterns-of-scandipwa#config)
* [Map approach for component types](/tutorials/video-tutorials/patterns-of-scandipwa#map-approach-for-component-types)
* [.container](/tutorials/video-tutorials/patterns-of-scandipwa#container)
* [Container structure: containerProps, containerFunctions](/tutorials/video-tutorials/patterns-of-scandipwa#container-structure-containerprops-containerfunctions)
* [.style](/tutorials/video-tutorials/patterns-of-scandipwa#style)

After watching this tutorial you should be able to discuss the following:

* Using VSCode extension for component bootstrap
* Top-level contents must be exported in .config file
* Map property principle
* Escaping for loops and lets

To follow along with this tutorial, you should start with the [environment set-up](broken://pages/-MVpv1d26tx-ZTJ79k5l). For this demonstration we’ll be using VSCode.

Run `yarn start` to start the development server and add an `index.js` file to the `src` folder:

```
📂<your-app-name>
 ┣ 📂i18n           
 ┣ 📂magento
 ┣ 📂node_modules   
 ┣ 📂public         
 ┣ 📂src
 ┃ ┗ 📜index.js     # new file            
 ┣ 📜composer.json
 ┣ 📜package.json
 ┗ 📜yarn.lock
```

The `index.js` file should look like this:

```javascript
import ReactDOM from 'react-dom';

ReactDOM.render(
    <UrlResolver />,
    document.getElementById('root')
);
```

You’ll see an error saying ‘UrlResolver’ is not defined. If you’ve followed along with the [environment set-up](https://docs.scandipwa.com/docs/environment-set-up.html), you’ll already have ScandiPWA Development Toolkit installed, which will be necessary for the rest of this tutorial.

To resolve the ‘not defined’ issue, press `ctrl + shift + P` to access the VSCode command pallette and type in ‘>Create new component’ and press enter.

You should then be prompted to type in your new component’s name, in this case it’s `UrlResolver`, select the `Contains business logic` feature and press enter.

The ScandiPWA Development Toolkit will generate a ScandiPWA `UrlResolver` component folder which will contain template files:

```javascript
📂<your-app-name>
 ┣ 📂i18n           
 ┣ 📂magento
 ┣ 📂node_modules   
 ┣ 📂public         
 ┣ 📂src
 ┃ ┣ 📂component/UrlResolver
 ┃ ┃ ┣ 📜index.js
 ┃ ┃ ┣ 📜UrlResolver.component.js
 ┃ ┃ ┣ 📜UrlResolver.container.js
 ┃ ┃ ┗ 📜UrlResolver.style.scss
 ┃ ┗ 📜index.js                 
 ┣ 📜composer.json
 ┣ 📜package.json
 ┗ 📜yarn.lock
```

If you have any issues with the imports, ScandiPWA Development Toolkit allows you to click on your issue and ‘Fix all auto-fixable problems’.

So, now we can go back to `src/index.js` and import the `UrlResolver`:

```javascript
import ReactDOM from 'react-dom';

import UrlResolver from 'Component/UrlResolver';

ReactDOM.render(
    <UrlResolver />,
    document.getElementById('root')
);
```

Notice that we don’t have to use the relative path, instead we can use an alias for the absolute path of the `Component` folder.

You can check out what path aliases are available by going to `node_modules/@scandipwa/scandipwa/src`. The folders here represent the available aliases and these are as follows: `component`, `query`, `route`, `store`, `style`, `type` and `util`. The alias for referencing these folders is simply the folder name - capitalized.

## .component

Using the previously created `component/UrlResolver` folder, let’s edit the `UrlResolver.component.js` file:

```javascript
// some stuff

render() {
    return (
        <div block="UrlResolver">
            Hello!
        </div>    
    );
}
```

If you go to Chrome `localhost:3000`, you’ll see the ‘Hello!’ being output there.

{% hint style="warning" %}
**NOTE**

The main tasks of `UrlResolver.component.js` are:

* Determining the page type
* Rendering the proper page
  {% endhint %}

So, we’ll need to take a URL using the location API and we’ll need to detect to which entity type the URL refers to, e.g. a page, a category or a product.

First, let’s look at page rendering. Let’s assume that there are multiple page types. How can we render them?

Since the `component.js` files are made for pure rendering, we’ll assume that the page type will be coming from props and we’ll not determine page type in the component.

The URL determination will be done in the `container.js` and we’ll tackle that a bit later.

Let’s assume that the `container` ships us a type as a prop:

```javascript
import PropTypes from 'prop-types';
import { PureComponent } from 'react';

import './UrlResolver.style';

class UrlResolver extends PureComponent {
    static propTypes = {
        // a propType of type string is required
        type: PropTypes.string.isRequired
    };

    render() {
        const { type } = this.props;

        if (type === 'product') {
            return 'product';
        } 
        
        if (type === 'category') {
            return 'category';
        }
        return 'cms_page';
    }
}
```

Let’s add individual render methods for each product type:

```javascript
import PropTypes from 'prop-types';
import { PureComponent } from 'react';

import './UrlResolver.style';

class UrlResolver extends PureComponent {
    static propTypes = {
        // a propType of type string is required
        type: PropTypes.string.isRequired
    };

    renderProduct() {
        return 'product';
    }

    renderCategory() {
        return 'category';
    }

    renderCmsPage() {
        return 'cms_page';
    }

    render404() {
        return '404';
    }

    render() {
        const { type } = this.props;

        if (type === PRODUCT_TYPE) {
            return this.renderProduct();
        } 
        
        if (type === CATEGORY_TYPE) {
            return this.renderCategory();
        }
        return this.renderCmsPage();
    }
    // some stuff
}
```

## .config

Add the product page types as constants in `UrlResolver.config.js`:

```javascript
export const PRODUCT_TYPE = 'product';
export const CATEGORY_TYPE = 'category';
export const CMS_PAGE_TYPE = 'cms_page';
```

We shouldn’t add the constants to the `component.js` file due to the fact that in order for `webpack` to be able to create smaller sized bundles, we need to add the constants that might be reused by different modules to the `config.js` file - outside of large modules.

`webpack` can’t split modules apart, so, if only a constant is needed, the bundle size would be minimized significally by using a constant-only file. In this case, creating a new file means creating a new module.

The issue with our `UrlResolver.component.js` file now is that the code repeats itself. An option is to write `switch` statements instead of `if` statements:

```javascript
// some stuff

render(){
    const { type } = this.props;

    switch (type) {
    case PRODUCT_TYPE:
        return this.renderProduct();
    case CATEGORY_TYPE:
        return this.renderCategory(); 
    case CMS_PAGE_TYPE:
        return this.renderCmsPage(); 
    default:
        return this.render404();
    }
}
```

Notice that ScandiPWA has a specific writing convention in place for `switch` statements - the cases should be on the same indentation level as the `switch` itself.

Since we haven’t imported the constants from the `config` file, we can use auto-fixer to ‘Fix this simple import-sort/sort problem’ and it’ll add the following to our imports:

```javascript
import PropTypes from 'prop-types';
import { PureComponent } from 'react';

import {
    CATEGORY_TYPE,
    CMS_PAGE_TYPE,
    PRODUCT_TYPE
} from './UrlResolver.config';

import './UrlResolver.style';
```

If we check-in with the browser, `localhost:3000` will display ‘404’ as the type of page hasn’t yet been passed, it’s undefined.

## Map approach for component types

The `switch` approach for component types is still not the most efficient way to go about rendering since we can’t quickly extend the method. The solution to this is to create a rendering map:

```javascript
class UrlResolver extends PureComponent {
    static propTypes = {
        type: PropTypes.string.isRequired
    };

    renderMap = {
        [CATEGORY_TYPE]: this.renderCategory.bind(this),
        [PRODUCT_TYPE]: this.renderProduct.bind(this),
        [CMS_PAGE_TYPE]: this.renderCmsPage.bind(this),
    };

    // other stuff
```

As you might know `this` can cause context loss, so we need to `bind` the type to the render method.

After creating the render map, we need to replace our `switch` statement:

```javascript
// some stuff

render(){
    const { type } = this.props;

    const renderFunction = this.renderMap[type];

    if (renderFunction) {
        return renderFunction();
    }

    return this.render404();

}
```

We can further optimize this by using the logical operator OR (||)

```javascript
// some stuff

render(){
    const { type } = this.props;

    const renderFunction = this.renderMap[type] || this.render404.bind(this);
    return renderFunction();
}
```

In both cases our render will return ‘404’ in the case if no type was found.

So, the finalized `component` file will look like this:

```javascript
import PropTypes from 'prop-types';
import { PureComponent } from 'react';

import {
    CATEGORY_TYPE,
    CMS_PAGE_TYPE,
    PRODUCT_TYPE
} from './UrlResolver.config';

import './UrlResolver.style';

class UrlResolver extends PureComponent {
    static propTypes = {
        type: PropTypes.string.isRequired
    };

    renderMap = {
        [CATEGORY_TYPE]: this.renderCategory.bind(this),
        [PRODUCT_TYPE]: this.renderProduct.bind(this),
        [CMS_PAGE_TYPE]: this.renderCmsPage.bind(this),
        // add a new [KEY] to object to extend
        // type constants are in config file 
    };

    renderProduct() {
        return 'product';
    }

    renderCategory() {
        return 'category';
    }

    renderCmsPage() {
        return 'cms_page';
    }

    render404() {
        return '404';
    }

    render(){
        const { type } = this.props;

        const renderFunction = this.renderMap[type] || this.render404.bind(this);
        return renderFunction();
    }
}

export default UrlResolver;
```

## .container

The `container` has all of the business logic inside of it. So, in order to determine the URL, we should go to the URL container. We can try to guess the URL type based on the URL, but we can also request the URL from the Magento URL resolver aka UrlRewrite\resolve.

For now, let’s just guess which type we’re referring to based on the location. So, to actually do it we should first understand the concept of a `container`.

## Container structure: containerProps, containerFunctions

A `container` file has two functions always defined:

```javascript
containerFunctions = {
    // getData: this.getData.bind(this)
};

containerProps = () => {
    // isDisabled: this._getIsDisabled()
};
```

`containerFunctions` is an object containing the mapping of a key that will be later passed to your component as a prop and the function that’ll be used to implement the prop.

The `getData` key will be passed as a prop to the `component`, where it’ll call it and then it’ll return some data. So, the logic itself will be located in the `container` and the `component` will simply call it.

We won’t be using the `containerFunctions` in this tutorial, so we can remove all references to it from the `container` file.

Next are the `containerProps` which are meant for props mapping. For example, if you have a property to define, like `isDisabled` or a type, you provide a function that will `get` this property for you.

Again, the property itself is used in the `component` for rendering, but the value retrieved from the `container`.

In order to determine the page type let’s do the following in the `container.js` file.

```
// add () to get a valid object returned, instead of a function
containerProps = () => ({
    type: this._getTypeFromURL()
});

_getTypeFromURL() {
    return PRODUCT_TYPE;
}

// some stuff
```

In order to use `PRODUCT_TYPE` we also need to import it in the `UrlResolver.container.js` file:

```javascript
import { PureComponent } from 'react';

import UrlResolver from './UrlResolver.component';
import { PRODUCT_TYPE } from './UrlResolver.config';
```

If we compile and go to `localhost:3000` in our browser, we should see ‘product’ now.

Now, we need to implement mapping itself. Let’s assume that the page we’re visiting contains the type and the URL. This means that we need to implement mapping again. This time instead of mapping to a function, let’s use regex, since it’s one of the faster ways to find strings.

```javascript
// some stuff

typeMap = {
    [CATEGORY_TYPE]: /category/,
    [PRODUCT_TYPE]: /product/,
    [CMS_PAGE_TYPE]: /page/,
};

containerProps = () => ({
    type: this._getTypeFromURL()
});

_getTypeFromURL() {
    // eslint-disable-next-line fp/no-let
    for (let i = 0; i < Object.entries.(this.typeMap).length; i++){
        const [type, regex] = Object.entries(this.typeMap)[i];

        // checking if the provided path name matches
        if (regex.test(window.location.pathname)) {
            return type;
        }
    }
    return ''; // will be handled as 404 by component
}
```

Disable any ESlint warnings and don’t forget to:

```javascript
import {
    CATEGORY_TYPE,
    CMS_PAGE_TYPE,
    PRODUCT_TYPE
} from './UrlResolver.config';
```

So in `localhost:3000` we’ll see ‘404’, but if we go to `localhost:3000/product` in our browser, we’ll see ‘product’.

A way of optimizing the for loop would be by defining the array beforehand:

```javascript
_getTypeFromURL() {
    const array = Object.entries.(this.typeMap);

    // eslint-disable-next-line fp/no-let
    for (let i = 0; i < array.length; i++){
        const [type, regex] = array[i];

        // checking if the provided path name matches
        if (regex.test(window.location.pathname)) {
            return type;
        }
    }
    return ''; // will be handled as 404 by component
}
```

Instead of the type map, we should make a type list that’ll work as an array. This will work much better for us, as it’ll be possible to loop through it right away:

```javascript
typeList = [
    {
        type: CATEGORY_TYPE,
        regex: /category/
    },
    {
        type: CMS_PAGE_TYPE,
        regex: /page/
    },
    {
        type: PRODUCT_TYPE,
        regex: /product/
    },
]

containerProps = () => ({
    type: this._getTypeFromURL()
});

_getTypeFromURL() {
    // eslint-disable-next-line fp/no-let
    for (let i = 0; i < this.typeList.length; i++){
        const { type, regex } = this.typeList[i];

        // checking if the provided path name matches
        if (regex.test(window.location.pathname)) {
            return type;
        }
    }
    return ''; // will be handled as 404 by component
}
```

Another way of optimizing would be by using array functions:

```javascript
_getTypeFromURL() {
    const { type } = this.typeList.find(
        ({ type, regex }) => regex.test(window.location.pathname)
    );
    return type;
}
```

An issue with using `find` is that it can return `null`. In this case, we’ll get a TypeError: Cannot destructure property ‘type’ because it’s undefined.

A solution would be to return an empty array in case nothing is found. If an empty array is destructured the type is ‘undefined’, this then can be handled by the `component`:

```javascript
_getTypeFromURL() {
    const { type } = this.typeList.find(
        ({ type, regex }) => regex.test(window.location.pathname)
    ) || {};

    return type; // will be handled as 404 if undefined by component
}
```

We can see that by using `typeList` the rest of our logic shrunk down as well. This is why we need to understand which data structure is needed before attempting to implement anything.

For example, arrays come in handy when you need to find something, but for rendering, maps are a better solution.

## .style

Let’s go back to the `UrlResolver.component.js` file and implement `renderType` as a separate function. This is needed because the `render` itself should return the style wrapper:

```javascript
// some stuff

    render404() {
        return '404';
    }

    renderType() {
        const { type = '404' } = this.props;
        const renderFunction = this.renderMap[type] || this.render404.bin(this);
        return (
            <article
                block="UrlResolver"
                elem="Type"
                mods={ { type } }
            >
                { renderFunction();}
            </article>
        );
    }

    render(){
        return (
            <main block="UrlResolver">
                { this.renderType() }
            </main>
        );
    }
}

export default UrlResolver;
```

Wrapping the `renderFunction` in `article` ensures that we’ll be able to refer to it using a BEM abstraction later on.

Go to `UrlResolver.style.scss` to apply some styles. If you want to take an in-depth look at ScandiPWA styling conventions, go [here](https://docs.scandipwa.com/docs/theme-styling.html).

```javascript
:root {
    --url-resolver-color: orange;
}

.UrlResolver {
    font-size: 20px;

    &-Type {
        color: var(--url-resolver-color);

        // type specific colors
        &_type {
            &_404 {
                color: red;
            }
        }
    }
}
```

The issue with `&_404 { color: red; }` is that we’re redefining a property, instead, we should redefine the CSS custom variable:

```javascript
:root {
    --url-resolver-color: orange;
}

.UrlResolver {
    font-size: 20px;

    &-Type {
        color: var(--url-resolver-color);

        // type specific colors
        &_type {
            &_404 {
                --url-resolver-color: red;
            }
        }
    }
}
```

What if we move the variable declaration a level up?

```javascript
:root {
    --url-resolver-color: orange;
}

.UrlResolver {
    font-size: 20px;
    color: var(--url-resolver-color);

    &-Type {
        &_type {
            &_404 {
                --url-resolver-color: red;
            }
        }
    }
}
```

Now, the logic will stop working and ‘404’ will be orange. As mentioned in [the previous tutorial](/tutorials/video-tutorials/styling-the-application) the CSS custom variables are resolved from the top of the file.

The first element it’ll look at is the and there the variable will be defined in `:root{}`. Going further down to, and further on we can see no declarations of this variable.

It’ll not care if the variable declaration appears inside the element, it only cares if the declaration happens above the element.

This is why color property declarations should appear on the same level or deeper than the variable re-declaration.

The only thing left to do is to get rid of the hardcoded red:

```javascript
:root {
    --url-resolver-color: orange;
    --url-resolver-404-color: red;
}

.UrlResolver {
    font-size: 20px;
    color: var(--url-resolver-color);

    &-Type {
        &_type {
            &_404 {
                --url-resolver-color: var(--url-resolver-404-color);
            }
        }
    }
}
```


# Dark Mode Extension

Implement a Dark Mode extension with the Scandi Plugin Mechanism!

One of the most powerful features of Scandi is its Plugin Mechanism, giving extensions virtually unlimited possibilities to alter the theme's behavior. In this tutorial, we will be using the Plugin Mechanism to implement an example extension that will allow the user to switch to a dark theme.

![](/files/-MYEKkeDwo6yuG9fX3Xa)

What you will learn:

* Writing Scandi plugins
* Creating and styling new components
* Working with Redux and browser local storage
* CSS variables
* Inverting the colors of a web app
* Scandi extension developing practices

## Prerequisites

For this tutorial, you will need to have a Scandi theme set up and running. If you don't, you can [set it up in minutes](https://docs.scandipwa.com/getting-started/getting-started/storefront-mode) by using the `create-scandipwa-app` (CSA) script. There is no need for a local Magento instance as long as you have an internet connection.

Before you learn to develop with Scandi, you need to have a basic understanding of JavaScript, a scripting language for the web. The [MDN developer docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/A_re-introduction_to_JavaScript) are a great resource for JavaScript documentation. You should also be familiar with [React, the UI library](https://reactjs.org/docs/hello-world.html) that Scandi uses. You don't need to read all of this documentation right now, but this is a great place to start if you get lost in code.

## Create a New Extension

The first thing we need to do to get started is creating an extension. An extension is a reusable package that can be installed on any Scandi theme. Once you are done with this tutorial, you will be able to use this extension in other projects, as long as their version is compatible — and even share it with others!

To create an extension, navigate to the root of your CSA application in the terminal. You can [create a new extension](https://docs.scandipwa.com/stack/extensions/creating-an-extension) using a `scandipwa` script:

```bash
scandipwa extension create scandi-dark-theme
```

{% hint style="info" %}
If you haven't installed the [Scandi CLI script](https://docs.scandipwa.com/dev-environment/scandipwa-cli), you can do so with `npm i -g scandipwa-cli`
{% endhint %}

This script will initialize a new extension named `packages/scandi-dark-theme` and configure your theme to install it. It will also enable it by setting `scandipwa.extensions["scandi-dark-theme"]` to `true` in `package.json`.

We should now verify that the extension is working properly. For testing purposes, we will create a [plugin](https://docs.create-scandipwa-app.com/extensions/application-plugins#to-create-a-plugin-for-class) that simply logs something to the console. In the `src/plugin` directory of your extension, create a file named `Header.component.plugin.js` with the following contents:

{% code title="src/plugin/Header.component.plugin.js" %}

```jsx
export const testPlugin = (args, callback, instance) => {
  console.log("Extension is working!");
  return callback(...args);
};

export default {
  "Component/Header/Component": {
    "member-function": {
      render: testPlugin,
    },
  },
};
```

{% endcode %}

Above, we define a plug-in `testPlugin` that logs a message to the console before passing control to the `callback` function. Once the callback function returns, we return the value it produces.

We then export a configuration that specifies that this plugin should be used for the `render` method of the [class with the namespace `Component/Header/Component`](https://github.com/scandipwa/scandipwa/blob/master/packages/scandipwa/src/component/Header/Header.component.js#L66).

{% hint style="info" %}
The plugin mechanism will wrap the `render` method of the Header component with our custom plugin - whenever `render` is called, our plugin will be used instead. However, we don't want to alter the value returned by `render`, so we must call `callback` (which represents the original `render` function, possibly wrapped in other plugins) and pass on its return value. If this still seems confusing, feel free to refer to the [plugin documentation](https://docs.create-scandipwa-app.com/extensions/application-plugins).
{% endhint %}

Now, whenever the `render` method of the Header component will be called, our message should appear in the console. And indeed it does! You might have to restart your app for the plugin to be registered.

## Define a New Redux Store

We want the user to be able to enable or disable dark mode, so we need a way for our application to keep track of whether Dark Mode is turned on. Since this state is global to the entire application, the best place to put it is in a Redux store.

[Redux](https://redux.js.org/introduction/core-concepts) is a global state container library. [Scandi uses Redux](https://docs.scandipwa.com/structure/building-blocks-summary/redux-stores) to keep track of its global state and has certain conventions for how Redux should be used.

Create a new store called `DarkMode`. When you have created the necessary boilerplate for the Redux store, we will create an action for it, and implement the reducer. Then, we will register the reducer in the global store.

{% hint style="info" %}
The quickest way to create a new store in VSCode is with the [ScandiPWA Development Toolkit add-on](https://marketplace.visualstudio.com/items?itemName=ScandiPWA.scandipwa-development-toolkit-vscode). Open your extension's directory in a new window - then press Ctrl+Shift+P to open the command pop-up and search for the `ScandiPWA: Create a store` command.
{% endhint %}

### Redux Action

In our Redux store, `DarkMode.action.js` should contain a function for creating actions. In Redux terminology, an action is a simple JavaScript object that describes a state update (but doesn't do anything itself).

In our case, we need an action creator for enabling or disabling Dark Mode.

{% code title="src/store/DarkMode/DarkMode.action.js" %}

```jsx
export const DARKMODE_ENABLE = 'DARKMODE_ENABLE';

/** @namespace ScandiDarkTheme/Store/DarkMode/Action/enableDarkMode */
export const enableDarkMode = (enabled) => ({
    type: DARKMODE_ENABLE,
    enabled
});
```

{% endcode %}

Nothing complicated here – `enableDarkMode(true)` returns `{ type: 'DARKMODE_ENABLE', enable: true }`, and `enableDarkMode(false)` returns `{ type: 'DARKMODE_ENABLE', enable: false }`. These Redux Actions are simple objects that don't do anything until we write code that interprets their meaning and updates the store, called reducers.

### Redux Reducer

The Reducer is the part that determines how the Redux store should be updated in response to actions.

{% code title="src/store/DarkMode/DarkMode.reducer.js" %}

```jsx
import { DARKMODE_ENABLE } from './DarkMode.action';

/** @namespace ScandiDarkTheme/Store/DarkMode/Reducer/getInitialState */
export const getInitialState = () => ({
    enabled: false
});

/** @namespace ScandiDarkTheme/Store/DarkMode/Reducer/DarkModeReducer */
export const DarkModeReducer = (state = getInitialState(), action) => {
    switch (action.type) {
    case DARKMODE_ENABLE:
        const { enabled } = action;

        return {
            enabled
        };

    default:
        return state;
    }
};

export default DarkModeReducer;
```

{% endcode %}

Our reducer maintains a single field in its state, `enabled`. Whenever it receives a `DARKMODE_SET`-type action, it returns (updates) the state with a new `enabled` value.

Note that this function will be called by Redux. Our only responsibility is to define how the state should update.

### getStaticReducers Plug-in

We have defined `DarkModeReducer`, but, like any function, it doesn't do anything until it's called. Reducer functions should be managed by Redux and some core Scandi code.

All the existing Reducers are registered in [`store/index.js`](https://github.com/scandipwa/scandipwa/blob/master/packages/scandipwa/src/store/index.js), in the function `getStaticReducers`. We can register our reducer by writing a plug-in for this function:

{% code title="src/plugin/getStaticReducers.plugin.js" %}

```jsx
import DarkModeReducer from "../store/DarkMode/DarkMode.reducer";

export const getStaticReducers = (args, callback) => ({
  ...callback(args),
  DarkModeReducer,
});

export default {
  "Store/Index/getReducers": {
    function: getStaticReducers,
  },
};
```

{% endcode %}

Now, the reducer should be registered. You can check with the Redux DevTools extension for [Chrome](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd) or [Firefox](https://addons.mozilla.org/en-US/firefox/addon/reduxdevtools/) that there is now a `DarkModeReducer` in the store. Next, we'll need a way for the user to change the value in this Redux store.

## Render a Dark Mode Toggle

We already wrote a `testPlugin` for the Header component that technically works, but doesn't do much. Instead of logging to the console, we want to render a toggle button for enabling dark mode:

{% code title="src/plugin/Header.component.plugin.js" %}

```jsx
import ModeToggleButton from "../component/ModeToggleButton";

import "./Header.style.plugin";

export const renderTopMenu = (args, callback, instance) => {
  return (
    <>
      {callback(...args)}
      <div block="Header" elem="DarkModeToggle">
        <ModeToggleButton />
      </div>
    </>
  );
};

export default {
  "Component/Header/Component": {
    "member-function": {
      renderTopMenu,
    },
  },
};
```

{% endcode %}

This code will render a `ModeToggleButton` right after the top menu. However, for this to work, we will also have to define the `ModeToggleButton` – otherwise, our plugin will attempt to render a non-existent component.

{% hint style="info" %}
How can we find the namespace to plug in to? This can be achieved by using React Developer Tools - a [browser extension](https://docs.scandipwa.com/dev-environment/development-setup) that allows you to inspect the rendered React elements. I knew that I wanted to render the button at the top of the page, so I checked which element renders it. Once I had the name of the element (Header), I could easily search for it in the codebase and find the corresponding namespace.
{% endhint %}

{% hint style="success" %}
You can create a new component in VSCode with the [ScandiPWA Development Toolkit add-on](https://marketplace.visualstudio.com/items?itemName=ScandiPWA.scandipwa-development-toolkit-vscode) by using the `ScandiPWA: Create a component` command. Enable the "connected to the global state" option.
{% endhint %}

When you've created the `ModeToggleButton` component, you will see that it contains several files:

| File                          |                                                                                                                               |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| ModeToggleButton.container.js | Contains business logic. Here we will define how the component should enable or disable dark mode                             |
| ModeToggleButton.component.js | Responsible for rendering a UI. Here, we will output the UI components (in this case, a button) and define their interactions |
| ModeToggleButton.style.scss   | A stylesheet for our component                                                                                                |
| index.js                      | Aliases the `.container` file                                                                                                 |

### The Container

Containers are for business logic. In our case, that means connecting to the Redux store to provide the current DarkMode state (enabled or disabled), and a function to dispatch actions to update the state. This will "connect" it to the Redux store we created in the previous section.

{% code title="src/component/ModeToggleButton/ModeToggleButton.container.js" %}

```jsx
import { connect } from "react-redux";

import { enableDarkMode } from "../../store/DarkMode/DarkMode.action";

import ModeToggleButton from "./ModeToggleButton.component";

/** @namespace ScandiDarkTheme/Component/ModeToggleButton/Container/mapStateToProps */
export const mapStateToProps = (state) => ({
  isDarkModeEnabled: state.DarkModeReducer.enabled,
});

/** @namespace ScandiDarkTheme/Component/ModeToggleButton/Container/mapDispatchToProps */
export const mapDispatchToProps = (dispatch) => ({
  enableDarkMode: (enabled) => dispatch(enableDarkMode(enabled)),
});

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(ModeToggleButton);
```

{% endcode %}

`mapStateToProps` has access to the Redux store - we want the component to get `isDarkModeEnabled` as a prop. `mapDispatchToProps` is connected to the Redux dispatcher - by dispatching `enableDarkMode`, we can now enable or disable the dark mode configuration in the Redux store.

### The Component

The `.component` file is responsible for rendering the user interface. In this case, we render a simple button – and when it's clicked, we toggle the Dark Mode Setting.

{% code title="src/component/ModeToggleButton/ModeToggleButton.component.js" %}

```jsx
import PropTypes from "prop-types";
import { PureComponent } from "react";

import "./ModeToggleButton.style";

/** @namespace ScandiDarkTheme/Component/ModeToggleButton/Component/ModeToggleButtonComponent */
export class ModeToggleButtonComponent extends PureComponent {
  static propTypes = {
    isDarkModeEnabled: PropTypes.bool.isRequired,
    enableDarkMode: PropTypes.func.isRequired,
  };

  render() {
    const { isDarkModeEnabled, enableDarkMode } = this.props;

    return (
      <button
        block="ModeToggleButton"
        aria-label={ __("Toggle Dark Mode") }
        onClick={() => enableDarkMode(!isDarkModeEnabled)}
      >
        { __("Toggle Dark Mode") }
      </button>
    );
  }
}

export default ModeToggleButtonComponent;
```

{% endcode %}

Now we have a button that toggles the state in our Dark Mode Redux store (you can check this with the Redux DevTools). Next, we need to implement a component that will read from this state and use a dark Scandi theme dark if Dark Mode is enabled.

## Implementing Dark Mode

There are several ways we can implement dark mode:

* Adjusting the values of all theme colors using [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties)
* Using the [filter property](https://developer.mozilla.org/en-US/docs/Web/CSS/filter) to invert the brightness of the entire app
* Using an all-white overlay with the `difference` [blending mode](https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode), resulting in inverted colors

Adjusting CSS variables would be a neat solution, and it would give us control over each color individually. However, in Scandi, many color values do not use the theme variables but are instead hardcoded. This limits how much control we can have on the app's colors via CSS variables, so this technique wouldn't work.

Another approach would be setting `filter: invert() hue-rotate(180deg)` on the root HTML element to invert the brightness, but keep the same hue for all colors. This would be an elegant solution, but after experimenting with it I noticed that, even though it worked well in Chromium, it can cause layout bugs in Firefox:

![](/files/-MYEKrvx09DUi2G-6Nhw)

After some testing, I concluded the last method — using an overlay with a blending mode — works well in Scandi, so is what we'll be using for the purposes of this tutorial. It feels a bit "hacky" but unlike the other techniques, it works.

This is how we will implement it:

1. Create a component that covers the page with a color-inverting overlay if Dark Mode is enabled
2. Create a plugin that would render this component on the page
3. Make some adjustments to fix colors that are broken as a result of Dark Mode

### Wrapping the App in a DarkModeProvider

First, we create a new component responsible for implementing dark mode, called `DarkModeProvider`. Like the dark mode toggle button, this component needs access to the dark mode configuration. However, it should render something different:

{% code title="src/component/DarkModeProvider/DarkModeProvider.component.js" %}

```jsx
// [...]
render() {
    const { children, isDarkModeEnabled } = this.props;

    // we specify a modifier called `isEnabled` in the `mods` prop
    // if isDarkModeEnabled is true, the modifier will be added, otherwise not
    return (
      <div block="DarkModeProvider" mods={{ isEnabled: isDarkModeEnabled }}>
        {children}
      </div>
    );
  }
// [...]
```

{% endcode %}

Now, let's create a plugin that wraps the entire application in a `DarkModeProvider`. We can do this by plugging into the `renderRouter` function of the `App` component – the entire application is rendered inside this.

{% code title="src/plugin/App.component.plugin.js" %}

```jsx
import DarkModeProvider from "../component/DarkModeProvider";

export const renderRouter = (args, callback, instance) => {
  return <DarkModeProvider key="router">{callback(...args)}</DarkModeProvider>;
};

export default {
  "Component/App/Component": {
    "member-function": {
      renderRouter,
    },
  },
};
```

{% endcode %}

The `DarkModeProvider` component makes use of the [Block-Element-Modifier (BEM)](https://docs.scandipwa.com/structure/building-blocks-summary/components/styling-components#the-bem-methodology) methodology. This is a set of guidelines for formatting CSS classes so that components can be easily styled, composed, and maintained.

In this example, the block is "DarkModeProvider" and the element has 1 modifier: `isEnabled`, which is either true or false. If it is false, the modifier does not get added. If it is true, the class gets an additional modifier: `DarkModeProvider_isEnabled`. We will be using this class selector in CSS, to ensure that dark mode is only active when the modifier is added:

{% code title="src/component/DarkModeProvider/DarkModeProvider.style.scss" %}

```css
.DarkModeProvider {
  // the ::after pseudo-element is what we use to invert all of the colors
  &::after {
    // by default (when dark mode is off), we don't want it to be visible
    // so we set the opacity to 0.
    // it is overridden with opacity: 1 in .DarkModeProvider_isEnabled::after
    opacity: 0;
    // defines a smooth transition when enabling or disabling dark mode
    transition: opacity ease-out 100ms;

    content: ""; // needed for ::after to be rendered at all

    // 1. make sure the element covers the entire page
    display: block;
    position: fixed;
    top: 0;
    bottom: 0;
    right: 0;
    left: 0;

    // 2. make sure the element is white, and "above" all the other layers
    z-index: 99999;
    background-color: white;

    // 3. magic. by using the difference blending mode with a white color,
    // all the colors in the app become inverted.
    // this works in all modern browsers.
    mix-blend-mode: difference;

    // we want click events to "pass through" this element,
    // so that it wouldn't interfere with the colors of the app
    pointer-events: none;
  }

  // styles that are only applied if dark mode is enabled
  &_isEnabled {
    &::after {
      // makes the inverting ::after element (from above) visible
      opacity: 1;
    }
  }
}
```

{% endcode %}

Now, our dark mode turns the theme dark, as expected. However, there are still some issues. As you might notice, all of the images appear inverted. In addition, all of the colors are inverted as well. Our next steps will be to fix these issues.

### Color Adjustments

To fix incorrect colors, we find the CSS variables responsible for incorrectly colored elements, and we invert their hues whenever dark mode is enabled:

{% code title="src/component/DarkModeProvider/DarkModeProvider.style.scss" %}

```css
// [...]
&_isEnabled {
    // adjust-hue is a SCSS function that "rotates" the hue of a specific color
    // in this case, we use it to create complementary colors of the same brightness
    --primary-error-color: #{adjust-hue(#dc6d6d, 180deg)};
    --primary-success-color: #{adjust-hue(#7fcd91, 180deg)};
    --primary-info-color: #{adjust-hue(#ffd166, 180deg)};

    --primary-base-color: var(
      --imported_primary_base_color,
      #{adjust-hue($default-primary-base-color, 180deg)}
    );
    --primary-dark-color: var(
      --imported_primary_dark_color,
      #{adjust-hue($default-primary-dark-color, 180deg)}
    );
    --primary-light-color: var(
      --imported_primary_light_color,
      #{adjust-hue($default-primary-light-color, 180deg)}
    );
    --secondary-base-color: var(
      --imported_secondary_base_color,
      #{adjust-hue($default-secondary-base-color, 180deg)}
    );
    --secondary-dark-color: var(
      --imported_secondary_dark_color,
      #{adjust-hue($default-secondary-dark-color, 180deg)}
    );
    --secondary-light-color: var(
      --imported_secondary_light_color,
      #{adjust-hue($default-secondary-light-color, 180deg)}
    );

    --link-color: var(--primary-base-color);
    --cart-overlay-totals-background: var(--secondary-base-color);
    --overlay-desktop-border-color: var(--primary-light-color);
    --menu-item-figure-background: var(--secondary-base-color);
    --menu-item-hover-color: var(--primary-base-color);
    --newsletter-subscription-placeholder-color: var(--secondary-dark-color);
    --newsletter-subscription-button-background: var(--link-color);
    --button-background: var(--primary-base-color);
    --button-border: var(--primary-base-color);
    --button-hover-background: var(--primary-dark-color);
    --button-hover-border: var(--primary-base-color);
```

{% endcode %}

### Re-inverting Images

To fix image appearance, we want to re-invert all images so that they appear normal when the entire page is inverted.

We plug into the `render` method of the `Image` component to wrap its contents in a ColorInverter component (which we haven't yet defined)

{% code title="src/plugin/Image.component.plugin.js" %}

```jsx
// wraps the output of the Image.render function in our ColorInverter component
export const render = (args, callback, instance) => {
  return <ColorInverter>{callback(...args)}</ColorInverter>;
};

// export a configuration specifying the namespace we want to plug in to
// as well as the type of plugin
export default {
  "Component/Image/Component": {
    "member-function": {
      render,
    },
  },
};
```

{% endcode %}

The ColorInverter component is very similar to our existing DarkModeProvider component - it inverts the colors of its child elements. The difference is that ColorInverter can use the `filter` property without causing bugs to invert the colors.

The container file is exactly the same as the one for DarkModeProvider (except for the different component name) — all it needs to is to provide the current Dark Mode state to the component.

The component file is also similar:

{% code title="src/component/ColorInverter/ColorInverter.component.js" %}

```jsx
// [...]
export class ColorInverterComponent extends PureComponent {
  static propTypes = {
    isDarkModeEnabled: PropTypes.bool.isRequired,
    children: ChildrenType.isRequired,
  };

  render() {
    const { isDarkModeEnabled, children } = this.props;

    // we specify a modifier called `isInverted` in the `mods` prop
    // if isDarkModeEnabled is true, the modifier will be added, otherwise not
    return (
      <div block="ColorInverter" mods={{ isInverted: isDarkModeEnabled }}>
        {children}
      </div>
    );
  }
}
// [...]
```

{% endcode %}

Now, in the stylesheet, all we need to do is invert the colors:

{% code title="src/component/ColorInverter/ColorInverter.style.scss" %}

```css
.ColorInverter {
  filter: invert(0);
  transition: filter ease-out 100ms;

  // these styles will only apply to elements whose Block is "ColorInverter"
  // and that have the { isInverted: true } prop
  // the corresponding CSS class for these elements is .ColorInverter_isInverted
  &_isInverted {
    filter: invert(1);
  }
}
```

{% endcode %}

Now, images look good regardless if dark mode is enabled:

![](/files/-MYEKvmrQb5L0F5zjurN)

## Exercises

Optional exercises you can complete to make sure you have understood the code:

1. We fixed product images, but configurable product color options are still inverted. Override the `ProductCard` and `ProductAttributeValue` components to fix these colors in PLP and PDP.
2. The dark mode toggle button can be distracting. Instead of rendering it at the top of the page, put it in the My Account page, in the Dashboard section.

{% hint style="info" %}
The code produced as part of this tutorial is available [here](https://gitlab.com/scandi-tutorials/dark-mode-extension)
{% endhint %}

## What Next?

Now that you have created your extension, you can use it on any of your projects, or [publish](https://marketplace.scandipwa.com/publish.html) it to share it with others. We hope this tutorial was useful for learning the principles of Scandi plugin development, and can't wait to see what you will create!

*Written by Reinis Mazeiks. Feel free to ask questions and share feedback in the* [*Slack channel*](https://scandipwa.slack.com/archives/C01STHKG5RQ)*. Thanks!*


# Deploying Native Apps

In order to publish a PWA as a stand alone native application in the App Store it’s required to add extra functionality which is not available by opening PWA in a web browser. Allowing to receive push notifications, scan QR codes, and access user contacts might be such features.

To achieve this goal it’s required to create a native iOS app containing `WKWebView` instance (references as webView in the following example code) and `WKScriptMessageHandler` protocol implementation (`JSMessageHandler` in example). PWA should provide a UI and send messages using Web Kit API. This API is injected by `WKWebView` itself and is accessed by calling a function in a following manner:&#x20;

```swift
window.webkit.messageHandlers.<message_name>.postMessage("Hello, native world!");
```

Example project is configured to respond to following messages: `exampleMessage`, `exampleMessageWithArgument`

To invoke them in the example project it’s required to make following function calls on PWA side:

```swift
window.webkit.messageHandlers.exampleMessage.postMessage();
window.webkit.messageHandlers.exampleMessageWithArgument.postMessage("Hello, native world!");
```

Returning to the native code of the example project let’s take a look at the `JSMessageHandler` class. It consists of the two parts. First one is implementation details and it declares constants with message names and declares two closure properties which should be called when a message from the PWA side is received.

Second part is the `userContentController` function and this is the `WKScriptMessageHandler` protocol function. It’s called when a message to Web Kit API is sent from the web side. `WKScriptMessage` is passed as an argument and this object contains the actual message name and arguments passed to it. It’s a `WKScriptMessageHandler` responsibility to identify the message and convert the argument passed if needed. In the following example it’s achieved by using a switch operator and calling a corresponding closure if message is identified.

```swift
class JSMessageHandler: NSObject, WKScriptMessageHandler {
    struct MessageNames {
        static let exampleMessage = "exampleMessage"
        static let exampleMessageWithArgument = "exampleMessageWithArgument"
    }
    var exampleAction: (() -> Void)?
    var exampleActionWithArgument:((String) -> Void)?
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        switch message.name {
        case MessageNames.exampleMessage:
            exampleAction?()
        case MessageNames.exampleMessageWithArgument:
            if let argument = message.body as? String {
                exampleActionWithArgument?(argument)
            } else {
                print("Wrong argument type for message \(message.name) : \(message.body)")
            }
        default:
            print("Unknown message: \(message.name)")
        }
}
```

Second class of the example project is a `ViewController` class which creates a  `JSMessageHandler` instance and configures `WKWebView` instance with it. Following function creates `WKWebViewConfiguration` instance:

```swift
func createWebViewConfiguration() -> WKWebViewConfiguration {
        let contentController = WKUserContentController()
        let handler = JSMessageHandler()
        handler.exampleAction = { [weak self] in
            print("exampleMessage was sent from PWA and is handled in native code")
            self?.webView.evaluateJavaScript("window.exampleActionCallback()", completionHandler: nil)
        }
        handler.exampleActionWithArgument = { [weak self] argument in
            print("exampleMessageWithArgument '\(argument)' was sent from PWA and is handled in native code")
            self?.webView.evaluateJavaScript("window.exampleActionWithArgumentCallback('\(argument + " Hello from native code!")')", completionHandler: nil)
        }
        contentController.add(handler, name: JSMessageHandler.MessageNames.exampleMessage)
        contentController.add(handler, name: JSMessageHandler.MessageNames.exampleMessageWithArgument)
        let config = WKWebViewConfiguration()
        config.userContentController = contentController
        return config
    }
```

First of all there is a `WKUserContentController` instance created. Afterwards a `JSMessageHandler` instance is created and its actions are populated. In this example these actions just report to the console and then call `window.exampleActionCallback()` or `window.exampleActionWithArgumentCallback()` correspondingly. It’s suggested that these functions are added by PWA and it will process the calls to them. In real world project this process most likely will be asynchronous and native code will call back to PWA after user interaction (getting QR code, accessing contacts etc)

Afterwards the content controller is instructed to pass the message handling for each expected message to create a `JSMessageHandler` instance. `WKWebViewConfiguration` is created, configured and returned afterwards.

```swift
lazy var webView: WKWebView = {
        let wv = WKWebView(frame: .zero, configuration: createWebViewConfiguration())

```

`WKWebView` instance is created as follows and should open PWA by URL later. The rest of the code is a standard UIKit / WebKit and is skipped for clarity. See attached example project. It’s configured to open a non-existing "<https://your.pwa.com>" site. You can change it to your own PWA URL.

{% file src="/files/-M\_-emSvr2gOhcFfcTgm" %}
TutorialPWA.zip
{% endfile %}


# Product 3D Model Extension

Implementing a Scandi extension for viewing product 3D models

With the flexible design of Scandi, you can develop functionality that neither Magento nor the Scandi theme support out-of-the-box. In this tutorial, we will be adding 3D models to the product data, and displaying them in a "3D Models" tab in the product page. This will be entirely implemented as a re-usable extension, so you will be able to install this functionality in any compatible project!

This tutorial consists of three parts. First, we create a Magento module enabling the admin to upload 3D model files for each product. Then, to make this data accessible to the Scandi frontend, we implement a GraphQL resolver to provide this data through an API. Finally, we create a frontend extension so that the 3D models can be seen on the frontend.

This is what we'll be creating:

![Example 3D models on the product page](/files/-M_521HBer5aLXxQcCkU)

\
3D Models used in the example above: "[National Park Binoculars - Hand Painted](https://sketchfab.com/3d-models/national-park-binoculars-hand-painted-37075392da5c410ab6944dcd42359a3d)" by [Adam Tabone](https://sketchfab.com/Adamoo) and "[Low poly McCree](https://sketchfab.com/3d-models/low-poly-mccree-38aedc02c0b2412babdc4d0eac7c6803)" by [Seafoam](https://sketchfab.com/seafoam)


# Part 1: Magento 3D Model Uploads

Adding 3D model upload functionality to the Magento Admin panel for products

Before we can display any 3D models on the frontend, we need the backend to be able to store these 3D models, and the administrator to manage them. Since this functionality is not provided in Magento by default, we will be creating a custom Magento module to implement this.

{% hint style="info" %}
This part of the tutorial is intended to be a quick run-through of the Magento module. We will not go too much in-depth, as the primary focus of this tutorial is on Scandi, which we will cover in Parts 2 and 3.
{% endhint %}

## Prerequisites

This tutorial assumes you have an instance of Magento you can work with. We will be using the [`create-magento-app` (CMA) ](https://docs.create-magento-app.com/)scripts for simplicity, but you can use any other setup – though you might need to adapt your workflow in that case, so we suggest to use CMA unless you know what you're doing.

It is desirable to have a basic understanding of how Magento works, especially it's common design patterns and concepts such as dependency injection. If you get lost, you can consult the [developer documentation](https://devdocs.magento.com/).

## Creating a Module

The first step is to [create a Magento module](https://devdocs.magento.com/videos/fundamentals/create-a-new-module/) where we can define our functionality in. You can either initialize it in `app/code`, or symlink a directory anywhere in your file-system, by [installing it as a composer local module](https://docs.scandipwa.com/magento/working-with-magento-modules#symlinking-with-composer).

Since all the backend functionality is exposed to Scandi through GraphQL, the convention is to name these backend modules with the `-graph-ql` postfix; hence we name our module `product-3d-graph-ql`. However, note that our module will include some additional functionality, such as the admin panel configuration.

## Creating a New Table

We want to be able to store multiple 3D models for each product, so we need a table to store this data. We can declaratively specify this [table in the db\_schema.xml file](https://devdocs.magento.com/guides/v2.4/extension-dev-guide/declarative-schema/db-schema.html#create-a-table):

{% code title="product-3d-graph-ql/etc/db\_schema.xml " %}

```markup
<?xml version="1.0"?>
<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">
    <table name="scandi_product_3d_model" resource="default" engine="innodb"
           comment="3D models associated with products">

        <column xsi:type="int" name="id" unsigned="true" nullable="false" identity="true"
                comment="ID"/>
        <column xsi:type="int" name="product_id" unsigned="true" nullable="false"/>
        <column xsi:type="text" name="url"/>
        <column xsi:type="text" name="file"/>

        <constraint xsi:type="primary" referenceId="PRIMARY">
            <column name="id"/>
        </constraint>

        <index referenceId="MODEL3D_PRODUCT_ID" indexType="btree">
            <column name="product_id"/>
        </index>
    </table>
</schema>
```

{% endcode %}

Here, we define a new table called `scandi_product_3d_model`, which has 4 columns. Each row has its own ID as well as a product ID that we will use to associate each 3D model with a product. In addition, the `file` column will be used for storing the original filename, and `url` will specify the location where this model is currently stored.

## Model & ResourceModel

Now we have a table to store our 3D model data, but we would also like an easy way to work with this data.

{% hint style="info" %}
Annoyingly, the word "model" has two different meanings in this context. A [database model](https://en.wikipedia.org/wiki/Database_model) is a class that helps with accessing data in the database. A [3D model](https://en.wikipedia.org/wiki/3D_modeling) is a computer representation of a three-dimensional object. To avoid confusion, we will always refer to 3D models by prefixing "3D". All other uses of "model" refer to the database model.
{% endhint %}

| Class                                                                                                                                                                                              | Purpose                                                                                                                                                                  |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [Scandi\Product3DGraphQl\Model\Model3D](https://gitlab.com/scandi-tutorials/product-3d-models/-/blob/main/product-3d-graph-ql/Model/Model3D.php)                                                   | A data class to represent a 3D model we can work with.                                                                                                                   |
| [Scandi\Product3DGraphQl\Model\ResourceModel\Model3D](https://gitlab.com/scandi-tutorials/product-3d-models/-/blob/main/product-3d-graph-ql/Model/ResourceModel/Model3D.php)                       | A [ResourceModel](https://devdocs.magento.com/guides/v2.4/architecture/archi_perspectives/persist_layer.html) responsible for loading & saving 3D models in the database |
| [Scandi\Product3DGraphQl\Model\ResourceModel\Model3D\Collection](https://gitlab.com/scandi-tutorials/product-3d-models/-/blob/main/product-3d-graph-ql/Model/ResourceModel/Model3D/Collection.php) | A collection of 3D models, so that we can fetch and work with multiple objects at the same time.                                                                         |
| [Scandi\Product3DGraphQl\Model\Model3DRepository](https://gitlab.com/scandi-tutorials/product-3d-models/-/blob/main/product-3d-graph-ql/Model/Model3DRepository.php)                               | An implementation of the [Repository pattern](https://devdocs.magento.com/guides/v2.4/extension-dev-guide/searching-with-repositories.html) for 3D models                |

## Admin Panel Upload

We want the admin to be able to upload new models for each product. This is a bit tricky, since Magento offers no easy way to add file upload attributes to products. However, we can work around this by creating a [product form UI Modifier](https://gitlab.com/scandi-tutorials/product-3d-models/-/blob/main/product-3d-graph-ql/Ui/DataProvider/Product/Form/Modifier/ProductModels.php), which configures a "3D Models" section in the product page on the fly. After configuring it in the [admin area etc.xml file](https://gitlab.com/scandi-tutorials/product-3d-models/-/blob/main/product-3d-graph-ql/etc/adminhtml/di.xml), the 3D Models sections is visible in the admin panel.

However, our custom UI modification does not currently handle saving the file uploads. For this, we need to [create](https://gitlab.com/scandi-tutorials/product-3d-models/-/blob/main/product-3d-graph-ql/Observer/Product/Save.php) and [configure](https://gitlab.com/scandi-tutorials/product-3d-models/-/blob/main/product-3d-graph-ql/etc/adminhtml/events.xml) an observer that saves the upload files after the product has been saved.

Now, the Magento admin panel configuration is implemented! The user is able to upload and remove 3D Models. Next, we will implement displaying these models in Scandi.

![](/files/-MZSF-4RQw1XOnlrVvUh)


# Part 2: GraphQL API

Expose product 3D models through the GraphQL API

At this point, the admin can upload and manage 3D models for each product, but these models are not yet visible to the frontend. However, before we can start implementing the frontend logic for product 3D models, we need to expose this data through an API.

{% hint style="info" %}
GraphQL is an API language has [several advantages](https://docs.scandipwa.com/about/why-scandipwa/challenges#api-complexity) over the conventional REST APIs – greater flexibility, better API documentation, type checking, and a simple query structure.
{% endhint %}

[Scandi uses GraphQL](https://docs.scandipwa.com/magento/developing-the-magento-backend) to fetch data from the backend. For example, to fetch product data, Scandi uses the `products` query:

{% code title="Example GraphQL query:" %}

```graphql
{
  products(search:"bag") {
    items {
      id
      name
    }
  }
}

```

{% endcode %}

{% code title="Corresponding response:" %}

```javascript
{
  "data": {
    "products": {
      "items": [
        {
          "id": 1,
          "name": "Joust Duffle Bag"
        },
        {
          "id": 8,
          "name": "Voyage Yoga Bag"
        },
        {
          "id": 4,
          "name": "Wayfarer Messenger Bag"
        }
      ]
    }
  }
}
```

{% endcode %}

This query already has multiple useful fields – we can query the product name, sku, price and other properties if we want. But now we want to add a brand-new queryable field: `model_3d_urls`. This field will return an array of strings, each representing the URL of a 3D model associated with the product.

{% hint style="info" %}
You can use a GraphQL client such as [Altair GraphQL Client](https://altair.sirmuel.design/) to make GraphQL queries easily. This can be useful when exploring an existing API as we did above, or to debug your own API, as we are about to do.

Make sure you set the API URL to <http://localhost/graphql> (assuming your M2 server is running at `localhost`) and you're good to go – you can try out the example query from above!
{% endhint %}

## Extending the GraphQL Schema

A GraphQL Schema is a document that describes the fields available in a GraphQL API. For example, there is a schema that specifies that the `items` field of the `products` query is an array of `ProductInterface`. There is another schema file that describes the fields that are part of `ProductInterface`:

{% code title="vendor/magento/module-catalog-graph-ql/etc/schema.graphqls (snippet)" %}

```graphql
interface ProductInterface
{
    id: Int
    name: String
    sku: String @doc(description: "A code assigned to a product")
    description: ComplexTextValue
    # [...]
}    
```

{% endcode %}

{% hint style="info" %}
Each field consists of a name for the field (like "id"), and a corresponding type (Int/String, etc.). This describes the values that the API is expected to have.
{% endhint %}

Because of the way GraphQL is setup in Magento, all of the schema files are merged together. This means that we can easily "extend" the `ProductInterface` type to include other fields as well. All we need to do is create a new GraphQL schema file in our module (in `etc/schema.graphqls`), and declare the additional `ProductInterface` fields there:

{% code title="etc/schema.graphqls" %}

```graphql
interface ProductInterface {
    model_3d_urls: [String!]!
}
```

{% endcode %}

And we have updated the GraphQL schema! Run `magento setup:upgrade` for Magento to update it.

{% hint style="info" %}
The exclamation mark (`!`) means that the field cannot be null. This means that values such as `model_3d_urls: null` and `model_3d_urls: [null, null]` are not valid. It is a good idea to tell GraphQL that we expect these values to be present on any product if that is the case, because this will result in an error if the field is not set, helping catch bugs early.
{% endhint %}

## Implementing the Field

Now, our `ProductInterface` type provides an additional field. We have made a "promise" to API users that they can query a `model_3d_urls` field on any product. Indeed, if we refresh the schema in our GraphQL client, we are now able to make a query such as this one:

```graphql
{
  products(search:"bag") {
    items {
      id
      name
      model_3d_urls
    }
  }
}
```

However, if we send the query, we will get an error – because there is nothing in the current resolver that would populate `model_3d_urls` with a value, and GraphQL is complaining that the data returned by the resolver does not match the schema we defined.

{% hint style="info" %}
A GraphQL Resolver is an implementation of the schema. It is the PHP code that actually handles PHP requests and returns a data object of the response.
{% endhint %}

The `products` query already has a resolver – that's the bit of code that returns the `items` so that we can see the `id`, `name` and other fields. Now, we want to "plug in" to the resolver so that it returns the additional data we want.

The best way to "plug in" depends on how the resolver is implemented. Sometimes, we have to use Magento plugins (interceptors) to "wrap around" the resolver and add additional data before it returns. In this case, however, there is a specific class whose purpose is to process the product data after it is generated, but before it is returned through GraphQL.

The product `DataPostProcessor` accepts an array of objects that implement the `ProductsDataPostProcessorInterface`. Then, whenever the product collection is processed, it passes the data through each of the processor objects, allowing them to modify the data.

We can use the dependency injection configuration file to inject an additional processor:

{% code title="etc/di.xmletc/di.xml" %}

```markup
<?xml version="1.0"?>
<config
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"
>
    <!-- [...] -->

    <type name="ScandiPWA\Performance\Model\Resolver\Products\DataPostProcessor">
        <arguments>
            <argument name="processors" xsi:type="array">
                <item name="model_3d_urls" xsi:type="object">
Scandi\Product3DGraphQl\Model\Resolver\Products\CollectionPostProcessor\Model3DProcessor
                </item>
            </argument>
        </arguments>
    </type>
</config>
```

{% endcode %}

Then, we can define that class and implement the functionality we want:

```php
<?php declare(strict_types=1);
namespace Scandi\Product3DGraphQl\Model\Resolver\Products\CollectionPostProcessor;

use Scandi\Product3DGraphQl\Helper\Model3DProvider;
use ScandiPWA\Performance\Api\ProductsDataPostProcessorInterface;

class Model3DProcessor implements ProductsDataPostProcessorInterface
{
    const MODEL_FIELD = 'model_3d_urls';

    /** @var Model3DProvider */
    protected $modelProvider;
    
    // [DI constructor omitted]

    public function process(
        array $products,
        string $graphqlResolvePath,
        $graphqlResolveInfo,
        ?array $processorOptions = []
    ): callable {
        // Collect relevant product IDs:
        $ids = [];
        foreach ($products as $product) {
            $ids[] = $product->getId();
        }

        // Get all 3D Models that belong to those IDs (1 SQL request)
        $modelsByProductId = $this->modelProvider->getModelsForProductIds($ids);

        // Return a function that adds a new field to the given product
        return function (&$product) use ($modelsByProductId) {
            $models = $modelsByProductId[$product['entity_id']];

            $urls = [];
            foreach ($models as $model) {
                $urls[] = $model->getResourceUrl();
            }

            $product[self::MODEL_FIELD] = $urls;
        };
    }
}
```

You can now verify that we can query the 3D Model URLs of the products and they will be returned correctly. Example response:

```javascript
{
  "data": {
    "products": {
      "items": [
        {
          "id": 1,
          "name": "Joust Duffle Bag",
          "model_3d_urls": [
            "http://localhost/media/scandi/product_3d_models/m/o/model1.glb",
            "http://localhost/media/scandi/product_3d_models/m/o/model2.glb"
          ]
        },
        {
          "id": 8,
          "name": "Voyage Yoga Bag",
          "model_3d_urls": []
        },
        {
          "id": 4,
          "name": "Wayfarer Messenger Bag",
          "model_3d_urls": []
        },
        {
          "id": 14,
          "name": "Push It Messenger Bag",
          "model_3d_urls": []
        }
      ]
    }
  }
}
```

{% hint style="success" %}
This works as expected – products with no 3D models get an empty array in `model_3d_urls` and products with 3D models have an array of valid model URLs.
{% endhint %}


# Part 3: Scandi Frontend

Display 3D models on the product page

## Prerequisites

Before you start with this section, you need to have a [Scandi theme set up](https://docs.scandipwa.com/getting-started/getting-started/storefront-mode) and [linked to your Magento instance](https://docs.create-magento-app.com/usage-guide/themes#2-linking-a-theme).

## Creating a Scandi Extension

We already have a backend module for our extension, but we need a separate Scandi extension Javascript module, responsible for implementing the frontend functionality. An extension is a reusable package that can be installed on any Scandi theme.

To create an extension, navigate to the root of your CSA application in the terminal. You can [create a new extension](https://docs.scandipwa.com/stack/extensions/creating-an-extension) using the `scandipwa` script:

```bash
scandipwa extension create 
```

{% hint style="info" %}
If you haven't installed the [Scandi CLI script](https://docs.scandipwa.com/dev-environment/scandipwa-cli), you can do so with `npm i -g scandipwa-cli`. It will automate certain tasks, making development faster.
{% endhint %}

This script will initialize a new extension named `packages/product-3d-scandi` and configure your theme to install it. It will also enable it by setting `scandipwa.extensions["product-3d-scandi"]` to `true` in `package.json`.

## Updating the Product Query

We have an API available to get the 3D models for each product. However, due to the design of GraphQL, this data won't be fetched unless the query specifically requests it (this helps save bandwidth on unused fields). Hence, the first step is to update the query so that 3D models are requested as well.

Unlike many other applications, Scandi builds queries at runtime, using its [GraphQL library](https://docs.scandipwa.com/structure/building-blocks-summary/constructing-graphql-queries#generating-graphql-queries). This means that updating the query for the purposes of an extension is as easy as [creating a plugin](https://docs.create-scandipwa-app.com/extensions/application-plugins).

The `ProductList` query creator is responsible for making product queries. In particular, we are interested in the `_getProductInterfaceFields` method, which is responsible for specifying the product fields we want to query.

{% hint style="info" %}
If need to plug into a query creator, but don't know its class, you can inspect the related source code to see which query it uses.
{% endhint %}

The current implementation of `_getProductInterfaceFields` already returns multiple fields, represented as an array of strings:

```javascript
[
  "id",
  "sku",
  "name",
  "type_id",
  "stock_status",
  // [...]
]
```

{% hint style="info" %}
More complex fields that also have sub-fields can be represented by an instance of the `Field` class. This will not be necessary for our purposes.
{% endhint %}

We want to wrap around this method, get the "original" array, and return an array with a new field, `"model_3d_urls"`, in addition to the original ones:

{% code title="src/plugin/ProductList.query.plugin.js" %}

```javascript
export const _getProductInterfaceFields = (args, callback, instance) => {
  const originalFields = callback(...args);
  return [...originalFields, "model_3d_urls"];
};

export default {
  "Query/ProductList": {
    "member-function": { _getProductInterfaceFields },
  },
};
```

{% endcode %}

{% hint style="info" %}
The plugin mechanism will wrap the `_getProductInterfaceFields` method of the `ProductList` query with our custom plugin – whenever `_getProductInterfaceFields`is called, our plugin will be used instead.

The original arguments will be provided in the `args` parameter, and the original function will be given in `callback` (`instance` is the class instance the method was called on, but we don't need it in this example).

We get the original fields returned by the function by calling the original function with the provided arguments (line 3). If this still seems confusing, feel free to refer to the [plugin documentation](https://docs.create-scandipwa-app.com/extensions/application-plugins).
{% endhint %}

{% hint style="success" %}
If you restart your app and open the Product Detail Page, you can verify in your browser's network tab that the `model_3d_urls` are indeed returned in the product request.
{% endhint %}

## Creating a 3D Model Component

We want to be able to display a carousel of 3D models on the product page, but we don't have any components with this functionality – so we need to create a new one. Again, we can use the CLI tool instead of creating the boilerplate manually

```
scandipwa create component Product3DViewer
```

Expected output:

```
     The following files have been created:
     src/component/Product3DViewer/Product3DViewer.component.js
     src/component/Product3DViewer/Product3DViewer.style.scss
     src/component/Product3DViewer/index.js
```

## Rendering a Product Tab

Now that we have a placeholder component, let's render it on the product page – we want it to appear in one of the tabs below the product image:

![](/files/-M_0Rqd5-4Qn3GiLgh2Q)

We can do this with a plugin, but first we need to find the function we need to plug in to. To do this, we can inspect the source code for the `ProductPage` component.

{% hint style="info" %}
How did I know to check the ProductPage component? I used the React developer tools to determine the name of the component, then found it in the source code directory. It's a good idea to install these[ developer extensions](broken://pages/-MOVDCW03Ud-lKz1G5Il#browser-plugins) to make such tasks easier.
{% endhint %}

After looking through the source code, it becomes clear that we need to focus on the `tabMap` property:

{% code title="Scandi base code: route/ProductPage/ProductPage.component.js" %}

```jsx
// [...]

/** @namespace Route/ProductPage/Component */
export class ProductPage extends PureComponent {
// [...]

    tabMap = {
        [PRODUCT_INFORMATION]: {
            name: __('About'),
            shouldTabRender: () => {
                const { isInformationTabEmpty } = this.props;
                return isInformationTabEmpty;
            },
            render: (key) => this.renderProductInformationTab(key)
        },
        [PRODUCT_ATTRIBUTES]: {
            name: __('Details'),
            shouldTabRender: () => {
                const { isAttributesTabEmpty } = this.props;
                return isAttributesTabEmpty;
            },
            render: (key) => this.renderProductAttributesTab(key)
        },
        [PRODUCT_REVIEWS]: {
            name: __('Reviews'),
            // Return false since it always returns 'Add review' button
            shouldTabRender: () => false,
            render: (key) => this.renderProductReviewsTab(key)
        }
    };
    
// [...]
}

export default ProductPage;
```

{% endcode %}

This -map pattern is fairly common in Scandi. It works by defining an object of similar, but distinct renderable elements. Then, a render function takes this data and renders it in the appropriate place.

This extra step of defining an object might seem counter-intuitive, but it will actually be quite helpful for our purposes. Now, to add a tab, all we have to do is add an entry to this `tabMap` field. This is easy to do with a member property plugin:

```jsx
import Product3DViewer
  from "../component/Product3DViewer/Product3DViewer.component";

export const PRODUCT_3D_MODEL_TAB = "PRODUCT_3D_MODEL_TAB";

export const render3dModelTab = (key, modelUrls) => {
  return (
    <Product3DViewer key={key} modelUrls={modelUrls}/>
  );
};

// a member plugin takes the original member value
// and returns the new value that the member should have
export const tabMap = (member, instance) => {
  return {
    ...member,
    [PRODUCT_3D_MODEL_TAB]: {
      name: __("3D Models"),
      shouldTabRender: () => {
        const { product: { model_3d_urls = [] } = {} } = instance.props;
        console.log(instance.props);

        // For some reason, shouldTabRender needs to return the opposite
        // of what you would think
        return !(model_3d_urls.length > 0);
      },
      render: (key) => {
        const { product: { model_3d_urls = [] } = {} } = instance.props;

        return render3dModelTab(key, model_3d_urls);
      },
    },
  };
};

export default {
  "Route/ProductPage/Component": {
    "member-property": { tabMap },
  },
};
```

{% hint style="info" %}
You might have noticed we used a function called `__`. Its job is to translate strings to the appropriate locale in production builds. It is a good practice to always wrap text visible to the user in `__("")`, in case you want to add translations later.
{% endhint %}

## Implementing 3D Rendering

Now we need to implement the main functionality – 3D model rendering. Since this is quite a complex feature, we will make use of a library to do all the heavy lifting for us. I considered implementing the viewer in [Three.js](https://threejs.org/), which has bindings for use in React. However, I found that there is a library that makes things even easier. The [model-viewer](https://modelviewer.dev/) library not only displays a 3D model, but also provides mouse interaction functionality and automatic rotation out-of-the-box.

It also integrates seamlessly with React – all we have to do to use it is to render a `<model-viewer>` element (this is not technically a React component, but we can still treat it like any other element).

First, we need to load the library. For some reason, installing it via `npm` and importing it as a module did not work – most likely, there is some conflict between the Webpack configuration in Scandi and model-viewer's module format. To work around this, we can load the module with a CDN:

{% code title="src/component/Product3DViewer/Product3DViewer.component.js" %}

```jsx
function loadModelViewer() {
  const MODULE_URL =
    "https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js";

  const script = document.createElement("script");
  script.src = MODULE_URL;
  script.type = "module";

  document.head.appendChild(script);
}

loadModelViewer();
```

{% endcode %}

Now we can render a 3D model in our Product3DViewer component:

```jsx
  render() {
    const { modelUrls } = this.props;

    const url = modelUrls[0];

    return (
      <div block="Product3DViewer">
        <model-viewer
          class="Product3DViewer-Model"
          src={url}
          alt={__("Product 3D Model")}
          auto-rotate
          camera-controls
        ></model-viewer>
      </div>
    );
  }
```

The only functionality left to implement is pagination between different models – as the admin might have uploaded more than one, but currently only the first one is shown. To implement this pagination, we need to store the currently active model index in the state:

```javascript
  state = {
    // Keep track of the currently visible model
    activeModelIndex: 0,
  };
```

Now, we can use `modelUrls[activeModelIndex]` to get the current model. By updating the state, we can change which model is active: `setState({ activeModelIndex: activeModelIndex + 1 })`, so let's implement a switcher enabling the user to navigate between different models:

```jsx
renderModelSwitcher() {
    const { modelUrls } = this.props;
    const { activeModelIndex } = this.state;

    // activeModelIndex is 0-indexed, but 1-indexed pages make more sense
    // so we add 1 to display it to the user
    const activeNumber = activeModelIndex + 1;
    
    const size = modelUrls.length;

    // We'll want to disable the "Previous" button for the first model
    // and the "Next" button for the last model
    const isFirst = activeModelIndex <= 0;
    const isLast = activeModelIndex >= size - 1;

    // Render no switcher if there is only one model
    if (size < 2) {
      return null;
    }

    return (
      <div block="Product3DViewer" elem="Switcher">
        <button
          block="Product3DViewer"
          elem="Button"
          disabled={isFirst}
          onClick={() =>
            this.setState({ activeModelIndex: activeModelIndex - 1 })
          }
        >
          {__("Previous")}
        </button>
        <span block="Product3DViewer" elem="ActiveIndex">
          {activeNumber}
        </span>
        <button
          block="Product3DViewer"
          elem="Button"
          disabled={isLast}
          onClick={() =>
            this.setState({ activeModelIndex: activeModelIndex + 1 })
          }
        >
          {__("Next")}
        </button>
      </div>
    );
  }
```

Whenever the user clicks a button that updates the state, the active model index is changed, and the component is re-rendered with the new model. When we put this all together, we get a component that can display a carousel of 3D models:

{% code title="src/component/Product3DViewer/Product3DViewer.component.js" %}

```jsx
import PropTypes from "prop-types";
import { PureComponent } from "react";

import "./Product3DViewer.style";

export class Product3DViewer extends PureComponent {
  // Define the props that this component expects to receive
  // Checked at runtime in development builds
  static propTypes = {
    modelUrls: PropTypes.arrayOf(PropTypes.string),
  };

  state = {
    activeModelIndex: 0,
  };

  renderModelSwitcher() {
    const { modelUrls } = this.props;
    const { activeModelIndex } = this.state;

    const activeNumber = activeModelIndex + 1;
    const size = modelUrls.length;

    const isFirst = activeModelIndex <= 0;
    const isLast = activeModelIndex >= size - 1;

    if (size < 2) {
      return null;
    }

    return (
      <div block="Product3DViewer" elem="Switcher">
        <button
          block="Product3DViewer"
          elem="Button"
          disabled={isFirst}
          onClick={() =>
            this.setState({ activeModelIndex: activeModelIndex - 1 })
          }
        >
          {__("Previous")}
        </button>
        <span block="Product3DViewer" elem="ActiveIndex">
          {activeNumber}
        </span>
        <button
          block="Product3DViewer"
          elem="Button"
          disabled={isLast}
          onClick={() =>
            this.setState({ activeModelIndex: activeModelIndex + 1 })
          }
        >
          {__("Next")}
        </button>
      </div>
    );
  }

  render() {
    const { modelUrls } = this.props;
    const { activeModelIndex } = this.state;

    const url = modelUrls[activeModelIndex];

    return (
      <div block="Product3DViewer">
        {this.renderModelSwitcher()}
        <model-viewer
          class="Product3DViewer-Model"
          src={url}
          alt={__("Product 3D Model")}
          auto-rotate
          camera-controls
        ></model-viewer>
      </div>
    );
  }
}

export default Product3DViewer;
```

{% endcode %}

{% hint style="info" %}
If you are new to React, this might seem a bit overwhelming. The [React tutorials](https://reactjs.org/tutorial/tutorial.html#what-is-react) are a great place to learn the basics of React.
{% endhint %}

![3D models on the product page!!](/files/-M_521HBer5aLXxQcCkU)

3D Models used in the example above: "[National Park Binoculars - Hand Painted](https://sketchfab.com/3d-models/national-park-binoculars-hand-painted-37075392da5c410ab6944dcd42359a3d)" by [Adam Tabone](https://sketchfab.com/Adamoo) and "[Low poly McCree](https://sketchfab.com/3d-models/low-poly-mccree-38aedc02c0b2412babdc4d0eac7c6803)" by [Seafoam](https://sketchfab.com/seafoam)

## What Next?

Congratulations, now you have learned how to implement completely new functionality in Scandi, from the Magento backend all the way to the Scandi React frontend. We can't wait to see what you'll create with this knowledge!

By the way, you can find the [final code created in this tutorial on Gitlab](https://gitlab.com/scandi-tutorials/product-3d-models).

*Written by Reinis Mazeiks. Feel free to ask questions and share feedback in the* [*Slack channel*](https://scandipwa.slack.com/archives/C01STHKG5RQ)*. Thanks for reading!*


# Social Share, Full Extension Development

In this tutorial, we going to develop one of the most popular extensions on different marketplaces SocialShare

**We going to implement:**

* Facebook, Facebook Counter
* Facebook Messenger, Requires API integration
* Telegram
* Whatsapp
* LinkedIn
* Email

**At Store Configuration under ScnadiPWA tab going to be created Social Share Page with module configuration**&#x20;

* General Settings&#x20;
  * Enabled
  * Button Type (Rounded, Square)
  * Button Size
  * Display on Yes/No
    * Homepage
    * Category Page
    * Product Page
* Facebook
  * Enable&#x20;
  * Enable Counter
* Facebook Messenger
  * Enable
  * Messenger app ID
* Telegram
  * Enable
* Whatsapp
  * Enable
* LinkedIn
  * Enable
* Email
  * Enable
  * Subject Suffix

**Every step going to be documented and separately branched**


# STEP-1 and 2 Creating Magento 2 Module

[**STEP-1**](https://github.com/GAkim/SocialShareGraphQl/tree/STEP-1)

{% embed url="<https://www.youtube.com/watch?v=N4fWRyYZ0SA>" %}

Let’s Create Empty Module Folder **app/code/ScandiPWA/SocialShareGraphQl**\
ScandiPWA is **\[VENDOR]** module provider \
SocialShareGrpahQl **\[MODULE\_NAME]**

\
[**STEP-2**](https://github.com/GAkim/SocialShareGraphQl/tree/STEP-2)

&#x20;Now we going to create a blank Magento module and register it in Magento

1. For that let’s create an **etc** folder in **app/code/ScandiPWA/SocailShareGraphQl** <- feather in text **\<MODULE ROOT>**
2. In **\<MODULE ROOT>/etc** we need to create file **module.xm**l with the following content

{% code title="ScandiPWA/SocialShareGraphQl/etc/module.xml" %}

```markup
<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
   <module name="ScandiPWA_SocialShareGraphQl" />
</config>
```

{% endcode %}

&#x20; 3\. Create **registration.php** in **\<MODULE ROOT>**

{% code title="ScandiPWA/SocialShareGraphQl/registration.php" %}

```php
<?php
use Magento\Framework\Component\ComponentRegistrar;

ComponentRegistrar::register(ComponentRegistrar::MODULE,
'ScandiPWA_SocialShareGraphQl',
__DIR__);
```

{% endcode %}

&#x20; 4\. Now we need to run `setup:upgrade` and find our module in output\
if you are running [CMA setup](https://docs.create-magento-app.com/) open console in **\<PROJECT ROOT>**  run `npm run cli`\
and then`m set:up`\
![](https://lh6.googleusercontent.com/Hj-UZsJHyDhAbFes9OYPe-n8mojaqjVzpGK5LSlhTJYVKCIkREtLAFDZ-OndAOsNLBRAci4JajO-xBSYqsdwAWKqWR38C8Eob78BMxVV8H6U3SgsSB-bm0QsBqKlar2O97dQBu7L)

**Congrats!!** \
**useful material:** [**Create a new Magento 2 module**](https://devdocs.magento.com/videos/fundamentals/create-a-new-module/) [**Working with Magento modules**](https://docs.scandipwa.com/developing-with-scandi/working-with-magento/working-with-magento-modules)\
[**Create Magento App**](https://docs.create-magento-app.com/)


# STEP-3 Backend Configurations Settings

In this step we going to create Module backend configurations

[**STEP-3**](https://github.com/GAkim/SocialShareGraphQl/tree/STEP-3)

{% embed url="<https://www.youtube.com/watch?v=HrMj1xDwOhQ>" %}

**For our implementation, we need several backend configurations**

* Enabled
* Button Type (Rounded, Square)
* Button Size
* Display on Yes/No
  * Homepage
  * Category Page
  * Product Page
* Facebook
  * Enable&#x20;
  * Enable Counter
* Facebook Messenger
  * Enable
  * Messenger app ID
* Telegram
  * Enable
* Whatsapp
  * Enable
* LinkedIn
  * Enable
* Email
  * Enable
  * Subject Suffix

Let’s create them.

1. Create **adminhtml** folder in **\<MODULE ROOT>/etc** and **system** folder in it
2. create **system.xml** in **\<MODULE ROOT>/etc/adminhtml**

{% code title="ScandiPWA/SocialShareGraphQl/etc/adminhtml/system.xml" %}

```markup
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
   <system>
       <tab id="scandipwa" translate="label" sortOrder="100">
           <label>Scandipwa</label>
       </tab>
       <include path="ScandiPWA_SocialShareGraphQl::system/social_share.xml"/>
   </system>
</config>
```

{% endcode %}

&#x20;  3\. Create **social\_share.xml** in **\<MODULE ROOT>/etc/adminhtml/system**

{% code title="ScandiPWA/SocialShareGraphQl/etc/adminhtml/system/social\_share.xml" %}

```markup
<?xml version="1.0"?>
<include xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_include.xsd">
   <section id="socialshare" translate="label" type="text" sortOrder="1000" showInDefault="1" showInWebsite="0" showInStore="0">
       <label>Social Share</label>
       <tab>scandipwa</tab>
       <resource>ScandiPWA_SocialShareGraphQl::social_share</resource>
<!--        General-->
       <group id="general" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0">
           <label>General Settings</label>
           <field id="enable" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0">
               <label>Enable</label>
               <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
           </field>

           <field id="rounded" translate="label" type="select" sortOrder="2" showInDefault="1" showInWebsite="0" showInStore="0">
               <label>Rounded Icons</label>
               <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
           </field>

           <field id="size" translate="label" type="text" sortOrder="3" showInDefault="1" showInWebsite="0" showInStore="0">
               <label>Icon Size</label>
               <comment>in pixel's</comment>
           </field>

           <field id="home_page" translate="label" type="select" sortOrder="4" showInDefault="1" showInWebsite="0" showInStore="0">
               <label>Display On HomePage</label>
               <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
           </field>

           <field id="category_page" translate="label" type="select" sortOrder="5" showInDefault="1" showInWebsite="0" showInStore="0">
               <label>Display On Product Page</label>
               <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
           </field>

           <field id="product_page" translate="label" type="select" sortOrder="6" showInDefault="1" showInWebsite="0" showInStore="0">
               <label>Display On Category Page</label>
               <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
           </field>
       </group>
<!--        FaceBook-->
       <group id="facebook" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0">
           <label>Facebook</label>
           <field id="enable" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0">
               <label>Enable Facebook</label>
               <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
           </field>

           <field id="enable_counter" translate="label" type="select" sortOrder="2" showInDefault="1" showInWebsite="0" showInStore="0">
               <label>Enable Facebook Counter</label>
               <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
           </field>
       </group>
<!--        Facebook Messenger-->
       <group id="facebook_messenger" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0">
           <label>Facebook Messenger</label>
           <field id="enable" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0">
               <label>Enable Facebook Messenger</label>
               <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
           </field>

           <field id="app_id" translate="text" type="text" sortOrder="2" showInDefault="1" showInWebsite="0" showInStore="0">
               <label>Facebook Messenger App ID</label>
           </field>
       </group>
<!--        Telegram-->
       <group id="telegram" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0">
           <label>Telegram</label>
           <field id="enable" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0">
               <label>Enable Telegram</label>
               <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
           </field>
       </group>
<!--        WhatsApp-->
       <group id="whatsapp" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0">
           <label>WhatsApp</label>
           <field id="enable" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0">
               <label>Enable WhatsApp</label>
               <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
           </field>
       </group>
<!--        LinkedIn-->
       <group id="linkedin" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0">
           <label>LinkedIn</label>
           <field id="enable" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0">
               <label>Enable LinkedIn</label>
               <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
           </field>
       </group>
<!--        Email-->
       <group id="email" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0">
           <label>Email</label>
           <field id="enable" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0">
               <label>Enable Email</label>
               <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
           </field>
           <field id="suffix" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0">
               <label>Email Subject Suffix</label>
           </field>
       </group>
   </section>
</include>
```

{% endcode %}

&#x20;  4\. Create **config.xml  in \<MODULE ROOT>/etc/**

{% code title="ScandiPWA/SocialShareGraphQl/etc/config.xml" %}

```markup
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
   <default>
       <socialshare>
           <general>
               <enable>0</enable>
               <rounded>1</rounded>
               <size>35</size>
               <home_page>0</home_page>
               <category_page>0</category_page>
               <product_page>1</product_page>
           </general>
           <facebook>
               <enable>0</enable>
               <enable_counter>0</enable_counter>
           </facebook>
           <facebook_messenger>
               <enable>0</enable>
           </facebook_messenger>
           <telegram>
               <enable>0</enable>
           </telegram>
           <whatsapp>
               <enable>0</enable>
           </whatsapp>
           <linkedin>
               <enable>0</enable>
           </linkedin>
           <email>
               <enable>0</enable>
               <suffix>ScandiPWA</suffix>
           </email>
       </socialshare>
   </default>
</config>

```

{% endcode %}

&#x20;  5\. run `cache:flush`

&#x20;  6\. Navigate to Magento backend dashboard STORE -> Settings -> Configuration, expand SCANDIPWA  you should see Social Share Option

![](https://lh3.googleusercontent.com/_xzQ8Oc-4DAhlh3I3PQ_Mp37RVtxzA9swORJ57uWAIuY2rFunOOjycgFmPpunwucWlW8gWvdPyMdX0i-GWemdaMtxw6IsHBHVgQlay-WAAivXWi-ExN7NcE6_Go-TEK4apIsLHne)


# STEP-4 Simple GraphQl and Resolver

The next few steps not going to be so visual, but very important, we going to create an Extension and establish the connection (communication) between this extension and our module.

[**STEP-4**](https://github.com/GAkim/SocialShareGraphQl/tree/STEP-4)

{% embed url="<https://www.youtube.com/watch?v=hIPoQ1uma2A>" %}

&#x20;In this particular ste&#x70;**,** we going to create a sample graphql shema and resolver.

1. Create **schema.graphqls** in **\<MODULE ROOT>/etc/**

{% code title="ScandiPWA/SocialShareGraphQl/etc/schema.graphqls" %}

```graphql
type Query {
   socialShare: socialShareType @resolver(class:"\\ScandiPWA\\SocialShareGraphQl\\Model\\Resolver\\SocialShare")
}

type socialShareType {
   enabled: String
}

```

{% endcode %}

&#x20;  2\. Create **Model** folder in **\<MODULE ROOT>** and **Resolver** folder in it.\
&#x20;  3\. Create **SocialShare.php** in **\<MODULE ROOT>/Model/Resolver**

{% code title="ScandiPWA/SocialShareGraphQl/Model/Resolver/SocialShare.php" %}

```php
<?php

declare(strict_types=1);

namespace ScandiPWA\SocialShareGraphQl\Model\Resolver;

use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;
use Magento\Framework\GraphQl\Query\Resolver\Value;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Framework\App\Config\ScopeConfigInterface;

/**
* @package ScandiPWA\SocialShareGraphQl\Model\Resolver
*/
class SocialShare implements ResolverInterface
{
   /**
    * @param Field $field
    * @param ContextInterface $context
    * @param ResolveInfo $info
    * @param array|null $value
    * @param array|null $args
    * @return string[]
    */
   public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
   {
       $result = [
           'enabled' => 'works'
       ];

       return $result;
   }
}

```

{% endcode %}

&#x20;  4\. run `cache:flush`

&#x20; 5\. Open GraphQl Client, for example, [**Altair GraphQL Client**](https://chrome.google.com/webstore/detail/altair-graphql-client/flnheeellpciglgpaodhkhmapeljopja/related?utm_source=chrome-ntp-icon) execute a query and check if you created       resolver is responding

![](https://lh6.googleusercontent.com/YMGG7SeRgIsYOnZE7mMhlH06EkD6umtMTZFhffRQ5J1ZeRY3YNTDznfB1FB-8UP-gL1BN2_NtSXCrWPiC4BH0JOMZ6KwkJsiipJSvuDw74CPzcNdMfam3AZqFod0BcX9meSYQzoN)

**useful materials:** [**GraphQl queries**](https://docs.scandipwa.com/structure/building-blocks-summary/constructing-graphql-queries?q=graphql)**,** [**Working with GraphQL**](https://docs.scandipwa.com/developing-with-scandi/working-with-magento/developing-the-magento-backend)<br>


# STEP-5 Creating Extension, Base Redux Store

In this step finally, we going to begin scandipwa extension development

[**STEP-5**](https://github.com/GAkim/SocialShareGraphQl/tree/STEP-5)

{% embed url="<https://www.youtube.com/watch?v=wm8qPqEypd8>" %}

1. Create scandipwa extension using scandipwa-cli, for that navigate to your theme folder, and execute `scandipwa extension create  scandipwa-socialshare`
2. In your scandipwa theme  **packages** folder you should locate **scandipwa-socialshare** extension folder\
   **NOTE. In my case ONLY for development purposes extension is located right in Magento module so my scandipwa/package.json file looks like**

```javascript
"dependencies": {
   "@scandipwa/scandipwa": "4.4.0",
   "scandipwa-socialshare": "file:../app/code/ScandiPWA/SocialShareGraphQl/scandipwa-socialshare"
},
"scandipwa": {
   "type": "theme",
   "locales": {
       "en_US": true
   },
   "parentTheme": "@scandipwa/scandipwa",
   "extensions": {
       "scandipwa-socialshare": true
   }
},
```

Your file should look like so.

```javascript
"dependencies": {
   "@scandipwa/scandipwa": "4.4.0",
   "scandipwa-socialshare": "file:packages/scandipwa-socialshare"
},
"scandipwa": {
   "type": "theme",
   "locales": {
       "en_US": true
   },
   "parentTheme": "@scandipwa/scandipwa",
   "extensions": {
       "scandipwa-socialshare": true
   }
},
```

&#x20;  3\. First, let’s create a query that going to query graph that we created in STEP-4\
&#x20;   create **query** folder in **scandipwa-socialshare/src** <- (in future **\<SOURCE>**) and **SocialShare.query.js** in it

{% code title="scandipwa-socialshare/src/query/SocialShare.query.js" %}

```javascript
import { Field } from 'Util/Query';

/** @namespace ScandipwaSocialshare/Query/SocialShare/Query/SocialShareQuery */
export class SocialShareQuery {
   getQuery() {
       return new Field('socialShare')
           .addFieldList(['enabled']);
   }
}

export default new SocialShareQuery();
```

{% endcode %}

4\. Now we going to create the SocialShare redux store, create **store** folder in **\<SOURCE>/**  and **SocialShare**  folder in **\<SOURCE>/store/**

5\. In **\<SOURCE>/store/SocialShare** create **SocialShare.action.js**

{% code title="scandipwa-socialshare/src/store/SocialShare/SocialShare.action.js" %}

```javascript
export const UPDATE_SOCIAL_SHARE = 'UPDATE_SOCIAL_SHARE';

/** @namespace Store/SocialShare/Action/updateSocialShare */
export const updateSocialShare = (socialShare) => ({
   type: UPDATE_SOCIAL_SHARE,
   socialShare
});
```

{% endcode %}

6\. In **\<SOURCE>/store/SocialShare** create **SocialShare.reducer.js**

{% code title="scandipwa-socialshare/src/store/SocialShare/SocialShare.reducer.js" %}

```javascript
import { UPDATE_SOCIAL_SHARE } from './SocialShare.action';

/** @namespace ScandipwaSocialshare/Store/SocialShare/Reducer/getInitialState */
export const getInitialState = () => ({
   socialShare: {
       enabled: 'not'
   }
});

/** @namespace ScandipwaSocialshare/Store/SocialShare/Reducer/SocialShareReducer */
export const SocialShareReducer = (
   state = getInitialState(),
   action
) => {
   const {
       type,
       socialShare
   } = action;

   switch (type) {
   case UPDATE_SOCIAL_SHARE:
       return {
           ...state,
           ...socialShare
       };

   default:
       return state;
   }
};

export default SocialShareReducer;
```

{% endcode %}

7\. And finally in **\<SOURCE>/store/SocialShare** create **SocialShare.dispatcher.js**

{% code title="scandipwa-socialshare/src/store/SocialShare/SocialShare.dispatcher.js" %}

```javascript
import { showNotification } from 'Store/Notification/Notification.action';
import BrowserDatabase from 'Util/BrowserDatabase';
import { QueryDispatcher } from 'Util/Request';
import { ONE_MONTH_IN_SECONDS } from 'Util/Request/QueryDispatcher';

import SocialShareQuery from '../../query/SocialShare.query';
import { updateSocialShare } from './SocialShare.action';

/** @namespace ScandipwaSocialshare/Store/SocialShare/Dispatcher/SocialShareDispatcher */
export class SocialShareDispatcher extends QueryDispatcher {
   __construct() {
       super.__construct('SocialShare');
   }

   onSuccess(data, dispatch) {
       if (data) {
           BrowserDatabase.setItem(data, 'SocialShare', ONE_MONTH_IN_SECONDS);
           dispatch(updateSocialShare(data));
       }
   }

   onError(error, dispatch) {
       dispatch(showNotification('error', __('Error fetching SocialShare!'), error));
   }

   prepareRequest() {
       return [
           SocialShareQuery.getQuery()
       ];
   }
}

export default new SocialShareDispatcher();
```

{% endcode %}

Unfortunately on this step, we can't see any visual outcome, the only thing that you can do, is to restart your frontend and ensure that it is still functional, believe in the next step we will have much more fun)).<br>


# STEP-6 Extension plugins

In this step, we going finally write our first Extension plugins, at first we will assign our reducer to the global redux store, and then will plugin into the Router container mapDispatchToProps.

[**STEP-6**](https://github.com/GAkim/SocialShareGraphQl/tree/STEP-6)

{% embed url="<https://www.youtube.com/watch?v=81u4HmDGamw>" %}

1. First, let’s add our reducer.\
   In your **\<SOURCE>/plugin** create the file **StoreReducer.plugin.js**

{% code title="scandipwa-socialshare/src/plugin/StoreReducer.plugin.js" %}

```javascript
import { SocialShareReducer } from '../store/SocialShare/SocialShare.reducer';

const getStaticReducers = (args, callback, instance) => ({
   ...callback(...args),
   SocialShareReducer
});

export const config = {
   'Store/Index/getStaticReducers': {
       function: getStaticReducers
   }
};

export default config;
```

{% endcode %}

&#x20;   2\. Open [**Redux DevTools**](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd) you should see **SocialShareReducer** object initial state.\
&#x20;look into **SocialShare.reducer.js lines 4 - 8**\
![](https://lh6.googleusercontent.com/TudkPuC8TXM3qUg30LbJLOJW2kdfF0abMc8nRUjW0tglRVjcXu9oS2AeRT7CIJgQjGwgi-KU5h8yQlkRFPQ4GGxPILtt2_RSTpgloUmNohdtGTjSWFPUGFKPsFDxuRoSwyOqhi76)

3\. What we want to succeed is to load all related to social share configurations on the initial page load.\
The same way that **ConfigReducer** does.\
In **Router Container** we can find ***initializeApplication*** method which is calling initial prop method **init();** here is our source theme **mapDispatchToProps&#x20;*****and initializeApplication***&#x20;

{% tabs %}
{% tab title=" mapDispatchToProps" %}
{% code title="src/component/Router/Router.container.js line 62 - 83" %}

```javascript
/** @namespace Component/Router/Container/mapDispatchToProps */
export const mapDispatchToProps = (dispatch) => ({
   updateMeta: (meta) => dispatch(updateMeta(meta)),
   updateConfigDevice: (device) => dispatch(updateConfigDevice(device)),
   init: () => {
       ConfigDispatcher.then(
           ({ default: dispatcher }) => dispatcher.handleData(dispatch)
       );
       MyAccountDispatcher.then(
           ({ default: dispatcher }) => dispatcher.handleCustomerDataOnInit(dispatch)
       );
       WishlistDispatcher.then(
           ({ default: dispatcher }) => dispatcher.updateInitialWishlistData(dispatch)
       );
       CartDispatcher.then(
           ({ default: dispatcher }) => dispatcher.updateInitialCartData(dispatch)
       );
       ProductCompareDispatcher.then(
           ({ default: dispatcher }) => dispatcher.updateInitialProductCompareData(dispatch)
       );
   }
});
```

{% endcode %}
{% endtab %}

{% tab title="initializeApplication " %}
{% code title="src/component/Router/Router.container.js lines 197 - 200" %}

```javascript
    initializeApplication() {
        const { init } = this.props;
        init();
    }
```

{% endcode %}
{% endtab %}
{% endtabs %}

Of course, we could plugin into **mapDispatchToProps** add new prop, then plugin into **initializeApplication** call original method and then our&#x73;**,** but it would be too boring, that why we going to plugin just in **mapDispatchToProps** and then going to mutate **init** prop method,\
let’s begin, In you **\<SOURCE>/plugin** create the file **RouterContainerMDTP.plugin.js**

{% code title="scandipwa-socialshare/src/plugin/RouterContainerMDTP.plugin.js" %}

```javascript
export const SocialShareDispatcher = import(
   /* webpackMode: "lazy", webpackChunkName: "dispatchers" */
   '../store/SocialShare/SocialShare.dispatcher'
);

/** @namespace ScandipwaSocialshare/Plugin/RouterContainerPlugin/mapDispatchToProps */
export const mapDispatchToProps = (args, callback, instance) => {
   const [dispatch] = args;
   const mdtp = callback(...args);
   const { init } = mdtp;

   mdtp.init = (...args) => {
       init(...args);
       SocialShareDispatcher.then(
           ({ default: dispatcher }) => dispatcher.handleData(dispatch)
       );
   };

   return mdtp;
};

export const config = {
   'Component/Router/Container/mapDispatchToProps': {
       function: mapDispatchToProps
   }
};

export default config;

```

{% endcode %}

4\. Now restart your frontend and open [**Redux DevTools**](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd) you should find **SocialShareReducer** object with value coming from the backend from our resolver check\
**ScandiPWA/SocialShareGraphQl/Model/Resolver/SocialShare.php line 27 -32**\
![](https://lh6.googleusercontent.com/ij0jLgsYB8SYuOxtZ0Cg4zRT3dvO-ZJJMXet5GWQIaU9Dy-Ysd3BpjEZpFuB5gwIuA9EDUQmfOU-uelO3rwcpNWdTM6XVnI6DUtaa8VAxoo7D8uxQfvgORFulUSFNYZaHi-Vmhb4)

Congrats, the most important part is done, we establish data transfer between our backend and frontend and, created a new reducer.<br>


# STEP-7 GraphQL types, Helpers

As we have successfully established a connection with backend and frontend, let's prepare and collect all configurations we created at STEP-3 transfer.

[**STEP-7**](https://github.com/GAkim/SocialShareGraphQl/tree/STEP-7)

{% embed url="<https://www.youtube.com/watch?v=lRj6fDwyuGs>" %}

1. First of all, we going to modify our **schema.graphqls** to gain an understanding of our data structure

{% code title="ScandiPWA/SocialShareGraphQl/etc/schema.graphqls" %}

```graphql
type Query {
   socialShare: socialShareType @resolver(class:"\\ScandiPWA\\SocialShareGraphQl\\Model\\Resolver\\SocialShare")
}

type socialShareType {
   socialShareConfig: socialShareConfig
   providers: [ socialShareProvider ]
}

type socialShareConfig {
   enabled: Boolean
   rounded: Boolean
   size: String
   categoryPage: Boolean
   productPage: Boolean
   homePage: Boolean
}

type socialShareProvider {
   id: String
   counter: Boolean
   additional: String
}

```

{% endcode %}

Ok now we know how our data structure will look like, then we going to pass data through graphql, will create a Helper which will provide socialShareConfig and providers and map all fields.\
&#x20;2\. Create **Helper** folder in **\<MODULE ROOT>** and **DataProvider.php** in it

{% code title="ScandiPWA/SocialShareGraphQl/Helper/DataProvider.php" %}

```php
<?php
declare(strict_types=1);

namespace ScandiPWA\SocialShareGraphQl\Helper;

use Magento\Framework\App\Config\ScopeConfigInterface;
/**
* @package ScandiPWA\SocialShareGraphQl\Helper
*/
class DataProvider
{
   const SOCIALSHARE_CONFIG = 'socialshare/general/';

   const SOCIALSHARE = 'socialshare/';

   const ENABLE = 'enable';

   const ROUNDED = 'rounded';

   const SIZE = 'size';

   const HOME_PAGE = 'home_page';

   const CATEGORY_PAGE = 'category_page';

   const PRODUCT_PAGE = 'product_page';

   const COUNTER = 'enable_counter';

   const SUFFIX = 'suffix';

   const APP_ID = 'app_id';

   const FB_MSG = 'facebook_messenger';

   const EMAIL = 'email';

   const PROVIDERS = [
       'facebook',
       'facebook_messenger',
       'telegram',
       'whatsapp',
       'linkedin',
       'email',
   ];

   /**
    * @var ScopeConfigInterface
    */
   protected $scopeConfig;

   /**
    * DataProvider constructor.
    * @param ScopeConfigInterface $scopeConfig
    */
   public function __construct(
       ScopeConfigInterface $scopeConfig
   ) {
       $this->scopeConfig = $scopeConfig;
   }

   /**
    * @return array
    */
   public function getSocialShareConfig() {

       return [
           'enabled' => $this->getConfig(self::SOCIALSHARE_CONFIG. self::ENABLE),
           'rounded' => $this->getConfig(self::SOCIALSHARE_CONFIG. self::ROUNDED),
           'size' => $this->getConfig(self::SOCIALSHARE_CONFIG. self::SIZE),
           'categoryPage' => $this->getConfig(self::SOCIALSHARE_CONFIG. self::CATEGORY_PAGE),
           'productPage' => $this->getConfig(self::SOCIALSHARE_CONFIG. self::PRODUCT_PAGE),
           'homePage' => $this->getConfig(self::SOCIALSHARE_CONFIG. self::HOME_PAGE)
       ];
   }

   /**
    * @return array
    */
   public function getSocialShareProviders() {
       $result = [];

       foreach (self::PROVIDERS as $provider) {
           $data = [];

           switch ($provider) {
               case self::FB_MSG:
                   $data = $this->getFacebookMessengerData();
                   break;
               case self::EMAIL:
                   $data = $this->getEmailData();
                   break;
               default:
                   $data = $this->getData($provider);
           }

           if($data) {
               array_push($result, $data);
           }
       }

       return $result;
   }

   /**
    * @return array|false
    */
   protected function getFacebookMessengerData() {
       $enabled = $this->getProviderConfig(self::FB_MSG, self::ENABLE);

       if($enabled) {
           return [
               'id' => self::FB_MSG,
               'additional' => $this->getProviderConfig(self::FB_MSG, self::APP_ID)
           ];
       }

       return false;
   }

   /**
    * @param $provider
    * @return array|false
    */
   protected function getData($provider) {
       $enabled = $this->getProviderConfig($provider, self::ENABLE);

       if($enabled) {
           return [
               'id' => $provider,
               'counter' => $this->getProviderConfig($provider, self::COUNTER)
           ];
       }

       return false;
   }

   /**
    * @return array|false
    */
   protected function getEmailData() {
       $enabled = $this->getProviderConfig(self::EMAIL, self::ENABLE);

       if($enabled) {
           return [
               'id' => self::EMAIL,
               'additional' => $this->getProviderConfig(self::EMAIL, self::SUFFIX)
           ];
       }

       return false;
   }

   /**
    * @param $path
    * @return mixed
    */
   protected function getConfig($path) {
        return $this->scopeConfig->getValue($path);
   }

   /**
    * @param $provider
    * @param $config
    * @return mixed
    */
   protected function getProviderConfig($provider, $config) {
       return $this->scopeConfig->getValue(self::SOCIALSHARE. $provider. '/'. $config);
   }
}

```

{% endcode %}

3\. Now we going to modify our resolver and request data from **DataProvider**

{% code title="ScandiPWA/SocialShareGraphQl/Model/Resolver/SocialShare.php" %}

```php
<?php

declare(strict_types=1);

namespace ScandiPWA\SocialShareGraphQl\Model\Resolver;

use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use ScandiPWA\SocialShareGraphQl\Helper\DataProvider;

/**
* @package ScandiPWA\SocialShareGraphQl\Model\Resolver
*/
class SocialShare implements ResolverInterface
{
   /**
    * @var DataProvider
    */
   protected $dataProvider;

   /**
    * SocialShare constructor.
    * @param DataProvider $dataProvider
    */
   public function __construct(
       DataProvider $dataProvider
   ) {
       $this->dataProvider = $dataProvider;
   }
   /**
    * @param Field $field
    * @param ContextInterface $context
    * @param ResolveInfo $info
    * @param array|null $value
    * @param array|null $args
    * @return string[]
    */
   public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
   {
       $result = [
           'socialShareConfig' => $this->dataProvider->getSocialShareConfig(),
           'providers' => $this->dataProvider->getSocialShareProviders()
       ];

       return $result;
   }
}

```

{% endcode %}

4\. Now again using  [**Altair GraphQL Client**](https://chrome.google.com/webstore/detail/altair-graphql-client/flnheeellpciglgpaodhkhmapeljopja/related?utm_source=chrome-ntp-icon) we going to check if we did everything right

![](https://lh5.googleusercontent.com/cYfSnrx8mjvr8rRAi6DVcLm5fEMvp56WHjmIppYhRTqEyicTj2z3OsvvsUqsVHF8Ha5bpKwss93bz6jOUD8ReUJKPYszpSF3OjFo6RiRlSQY_Tu-KfVe1szx5m5R3-1996twoiK1)




---

[Next Page](/llms-full.txt/1)

