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