Skip to main content

Using React Components

Nx-Shopify leverage from Nx workspaces power and provides customization capabilities in order to integrate technologies like React. The following guide will take you step by step in how to use React components in your Shopify theme in a clean, simple and well-architected way.

Preparing the workspace#

Having Nx-Shopify installed in your Nx workspace, let's generate a theme called my-theme to integrate React in it.

$ nx g @trafilea/nx-shopify:theme my-theme

Installing dependencies#

Taking advantage of Nx, we will use the Nx plugin for React to integrate react in our Shopify theme.

Install the plugin in your workspace:

# npm
$ npm install --save-dev @nrwl/react
# yarn
$ yarn add --dev @nrwl/react
# pnpm
$ pnpm add --save-dev @nrwl/react

Then initialize the plugin:

# npm
$ nx generate @nrwl/react:init
$ nx g @nrwl/react:init # same
tip

Learn more about the usage of the @nrwl/react plugin at Nx docs site

Configuring your theme for React#

In order to support react components, make sure the following properties are configured in the apps/my-theme/tsconfig.json file

apps/my-theme/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
+ "jsx": "react-jsx",
+ "allowJs": true, (this one is optional)
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

And your apps/my-theme/tsconfig.app.json should look similar to this:

apps/my-theme/tsconfig.app.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": ["node"]
},
+ "files": [
+ "../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
+ "../../node_modules/@nrwl/react/typings/image.d.ts"
+ ],
"exclude": ["**/*.spec.ts", "**/*.spec.tsx"],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

Note: set the paths according to your theme's location in the workspace.

Finally, add the @nrwl/nx/react eslint plugin to the theme's .eslintrc.json config file.

apps/my-theme/.eslintrc.json
{
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"parserOptions": {
"project": ["apps/my-theme/tsconfig.*?.json"]
},
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

If you are planning to have react components within your theme project (more info about this below) then create a .babelrc file with the following content:

apps/my-theme/.babelrc
{
"presets": [
[
"@nrwl/react/babel",
{
"runtime": "automatic"
}
]
]
}
info

Depending on your choosen styling option you may need add one (or many) additional plugins to your .babelrc.

Next modify your apps/my-theme/jest.config.js like the following:

apps/my-theme/jest.config.js
module.exports = {
displayName: 'my-theme',
preset: '../../jest.preset.js',
transform: {
'^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest',
'^.+\\.[tj]sx?$': 'babel-jest',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/apps/my-theme',
};

Creating your React components#

You can make use of the @nrwl/react Nx plugin to generate your React components. Where you are going to generate your components depends on your choice, here you have multiple options: generate the components in your theme project, create React libraries or both!

warning

When generating your components, you can make use of SASS or any styles in JS (styled-components, emotion, styled-jsx, etc) styling option. Other styling options are not supported.

Creating components in your theme project#

If you are going to have your components in your theme project you can simply generate them using the nx cli. See the following example:

nx generate @nrwl/react:component hello-world --project my-theme --export false
nx g @nrwl/react:component foo --project my-theme --export false
nx g @nrwl/react:c bar --project my-theme --export false

And this will create the following components

apps/my-theme/src/app
โ”œโ”€โ”€ bar
โ”‚ โ”œโ”€โ”€ bar.spec.tsx
โ”‚ โ””โ”€โ”€ bar.tsx
โ”œโ”€โ”€ foo
โ”‚ โ”œโ”€โ”€ foo.spec.tsx
โ”‚ โ””โ”€โ”€ foo.tsx
โ””โ”€โ”€ hello-world
โ”œโ”€โ”€ hello-world.spec.tsx
โ””โ”€โ”€ hello-world.tsx

Notice that, because of how the @nrwl/react plugin works, your React components will live under the src/app directory by default. You can specify where do you want your component to be generated with the --directory option in the generate command. Example:

nx generate @nrwl/react:component hello-world --project my-theme --export false --directory components
nx g @nrwl/react:component foo --project my-theme --export false --directory components/example
nx g @nrwl/react:c bar --project my-theme --export false --directory components/example

Now your components will be located at

apps/my-theme/src/components
โ”œโ”€โ”€ example
โ”‚ โ”œโ”€โ”€ bar
โ”‚ โ”‚ โ”œโ”€โ”€ bar.module.scss
โ”‚ โ”‚ โ”œโ”€โ”€ bar.spec.tsx
โ”‚ โ”‚ โ””โ”€โ”€ bar.tsx
โ”‚ โ””โ”€โ”€ foo
โ”‚ โ”œโ”€โ”€ foo.module.scss
โ”‚ โ”œโ”€โ”€ foo.spec.tsx
โ”‚ โ””โ”€โ”€ foo.tsx
โ””โ”€โ”€ hello-world
โ”œโ”€โ”€ hello-world.module.scss
โ”œโ”€โ”€ hello-world.spec.tsx
โ””โ”€โ”€ hello-world.tsx

Creating components in React libraries#

Using this approach, you can use the @nrwl/react plugin to create React libraries as you would normally do. Let's create a feature lib for our theme that would display a banner on the top of our store.

nx g @nrwl/react:lib feature-banner --directory my-theme

This will create the following library

libs
โ””โ”€โ”€ my-theme
โ””โ”€โ”€ feature-banner
โ”œโ”€โ”€ jest.config.js
โ”œโ”€โ”€ README.md
โ”œโ”€โ”€ src
โ”‚ โ”œโ”€โ”€ index.ts
โ”‚ โ””โ”€โ”€ lib
โ”‚ โ”œโ”€โ”€ my-theme-feature-banner.module.scss
โ”‚ โ”œโ”€โ”€ my-theme-feature-banner.spec.tsx
โ”‚ โ””โ”€โ”€ my-theme-feature-banner.tsx
โ”œโ”€โ”€ tsconfig.json
โ”œโ”€โ”€ tsconfig.lib.json
โ””โ”€โ”€ tsconfig.spec.json

Then you can generate as many additional components as you need to your react library. Example:

nx g @nrwl/react:c banner-text --project my-theme-feature-banner

And use it in your main lib component

libs/my-theme/feature-banner/src/lib/my-theme-feature-banner.tsx
import React from 'react';
import './my-theme-feature-banner.module.scss';
import BannerText from './banner-text/banner-text';
/* eslint-disable-next-line */
export interface MyThemeFeatureBannerProps {}
export function MyThemeFeatureBanner(props: MyThemeFeatureBannerProps) {
return (
<div>
<h1>Welcome to my-theme-feature-banner!</h1>
<BannerText></BannerText>
</div>
);
}
export default MyThemeFeatureBanner;

Rendering React components in your theme#

Now that everything is configured and your React components are ready to be used, it's time to actually render them in your theme.

As your theme code is actually TypeScript (not TSX syntax) creating a wrapper function for your root components can help you easily import them. For the above example:

libs/my-theme/feature-banner/src/lib/my-theme-feature-banner.tsx
import React from 'react';
import './my-theme-feature-banner.module.scss';
import BannerText from './banner-text/banner-text';
/* eslint-disable-next-line */
export interface MyThemeFeatureBannerProps {}
export function MyThemeFeatureBanner(props: MyThemeFeatureBannerProps) {
return (
<div>
<h1>Welcome to my-theme-feature-banner!</h1>
<BannerText></BannerText>
</div>
);
}
export function MyThemeFeatureBannerWrapper(props: MyThemeFeatureBannerProps) {
return <MyThemeFeatureBanner {...props} />;
}
export default MyThemeFeatureBanner;

Now you can call this function to render your component in your theme-global.module.ts, a layout TS files, a template TS file, etc... depending on your use case. Let's, for example, render the above component in our theme layout.

Add the root element where the component will be rendered in the respective liquid file. For this case, add the following to the theme.liquid layout file.

apps/my-theme/src/theme/layout/theme/theme.liquid
<!DOCTYPE html>
<html>
<head>
<title>{{ page_title }}</title>
... {% render 'script-tags' %} {% render 'style-tags' %} {{
content_for_header }}
<!-- Header hook for plugins -->
</head>
<body>
<div id="banner-root"></div>
...
<main role="main">{{ content_for_layout }}</main>
...
<script>
window.addEventListener('DOMContentLoaded', function() {
window.themeBootstrap({
themeLayoutName: 'theme',
themeTemplateName: '{{ template }}',
themeContext: {% render 'theme-context' %},
loadGlobal: true,
});
});
</script>
</body>
</html>

Now, import the render function from the react-dom package and your component wrapper function in the theme.layout.ts file.

Next, call the render function and pass the return value of the component wrapper function (that receives the components props) and the root element.

apps/my-theme/src/theme/layout/theme/theme.layout.ts
import { ThemeModule, ThemeContext, ThemeOnReady } from '@myorg/my-theme/core';
import { render } from 'react-dom';
import {
MyThemeFeatureBannerWrapper,
MyThemeFeatureBannerProps,
} from '@myorg/my-theme/feature-banner';
import './theme.layout.scss';
export class ThemeLayout extends ThemeModule implements ThemeOnReady {
constructor(context: ThemeContext) {
super(context);
}
onReady() {
const bannerProps: MyThemeFeatureBannerProps = {};
render(
MyThemeFeatureBannerWrapper(bannerProps),
document.getElementById('banner-root')
);
}
}

Finally, run the serve target to view your theme with your React components working!

nx serve my-theme -o

Bonus: passing the ThemeContext to React libraries#

It may be possible that you would like to have your ThemeContext (or part of it) available inside a React library used by your theme.

To achieve this, you can certainly define the typings of your components props object in the react library. However, it may come handy to share a single ThemeContext type definition across your theme project and your libraries. Let's go through the process.

First you will need to create a shared library where your ThemeContext type definitons are going to live.

nx g @nrwl/workspace:lib theme-context --directory my-theme/shared

You will get a lib structure like the following (you can delete the test files). The ThemeContext will be defined in the my-theme-shared-theme-context.ts file.

libs/my-theme/shared
โ””โ”€โ”€ theme-context
โ”œโ”€โ”€ jest.config.js
โ”œโ”€โ”€ README.md
โ”œโ”€โ”€ src
โ”‚ โ”œโ”€โ”€ index.ts
โ”‚ โ””โ”€โ”€ lib
โ”‚ โ””โ”€โ”€ my-theme-shared-theme-context.ts
โ”œโ”€โ”€ tsconfig.json
โ”œโ”€โ”€ tsconfig.lib.json
โ””โ”€โ”€ tsconfig.spec.json

Add the type definitions to the my-theme-shared-theme-context.ts file:

libs/my-theme/shared/theme-context/src/lib/my-theme-shared-theme-context.ts
export interface BannerContext {
globalMessage: string;
}
export interface ThemeContext {
themeName: string;
banner?: BannerContext;
}
note

You can, and perhaps you should, organize the portions of your ThemeContext interface in different files according to your needs. Make sure they are exported in the library's index.ts file.

Go to the apps/my-theme/src/core/theme-context.ts file, remove the current content and export everything from the theme context library.

apps/my-theme/src/core/theme-context.ts
export * from '@myorg/my-theme/shared/theme-context';

The above will make your theme work with the types defined in the library without requiring to change the imports from all of the theme blocks (layouts, templates, sections and snippets).

Go to your React libray and add the ThemeContext (or part of it) to your components props type definition and make use of it. Notice the interface is imported from the shared library.

libs/my-theme/feature-banner/src/lib/my-theme-feature-banner.tsx
import React from 'react';
import './my-theme-feature-banner.module.scss';
import BannerText from './banner-text/banner-text';
import { BannerContext } from '@myorg/my-theme/shared/theme-context';
/* eslint-disable-next-line */
export interface MyThemeFeatureBannerProps {
banner: BannerContext;
}
export function MyThemeFeatureBanner(props: MyThemeFeatureBannerProps) {
const { banner } = props;
return (
<div>
<h1>Welcome to my-theme-feature-banner!</h1>
<BannerText>{banner.globalMessage}</BannerText>
</div>
);
}
export function MyThemeFeatureBannerWrapper(props: MyThemeFeatureBannerProps) {
return <MyThemeFeatureBanner {...props} />;
}
export default MyThemeFeatureBanner;

Now back in the theme.layout.ts file, you just need to pass the respective context in the component props to the component wrapper function:

apps/my-theme/src/theme/layout/theme/theme.layout.ts
import { ThemeContext, ThemeModule, ThemeOnReady } from '@myorg/my-theme/core';
import {
MyThemeFeatureBannerProps,
MyThemeFeatureBannerWrapper,
} from '@myorg/my-theme/feature-banner';
import { render } from 'react-dom';
import './theme.layout.scss';
export class ThemeLayout extends ThemeModule implements ThemeOnReady {
constructor(context: ThemeContext) {
super(context);
}
onReady() {
console.log('MyTheme | Theme Layout: onReady() called');
const bannerProps: MyThemeFeatureBannerProps = {
banner: this.context.banner,
};
render(
MyThemeFeatureBannerWrapper(bannerProps),
document.getElementById('banner-root')
);
}
}

And you're ready to go!