#5 Patterns of ScandiPWA
Last updated
Last updated
In this tutorial we will talk about the main component files in ScandiPWA:
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. 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:
The index.js
file should look like this:
You’ll see an error saying ‘UrlResolver’ is not defined. If you’ve followed along with the environment set-up, 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:
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
:
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.
Using the previously created component/UrlResolver
folder, let’s edit the UrlResolver.component.js
file:
If you go to Chrome localhost:3000
, you’ll see the ‘Hello!’ being output there.
NOTE
The main tasks of UrlResolver.component.js
are:
Determining the page type
Rendering the proper page
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:
Let’s add individual render methods for each product type:
Add the product page types as constants in UrlResolver.config.js
:
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:
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:
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.
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:
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:
We can further optimize this by using the logical operator OR (||)
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:
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
.
A container
file has two functions always defined:
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.
In order to use PRODUCT_TYPE
we also need to import it in the UrlResolver.container.js
file:
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.
Disable any ESlint warnings and don’t forget to:
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:
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:
Another way of optimizing would be by using array functions:
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
:
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.
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:
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.
The issue with &_404 { color: red; }
is that we’re redefining a property, instead, we should redefine the CSS custom variable:
What if we move the variable declaration a level up?
Now, the logic will stop working and ‘404’ will be orange. As mentioned in the previous tutorial 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: