- Explore some of the Front End tech that drives Shopify
- Create a form using Shopify React Component Library Polaris
- Extend the form using react-form hook from Shopify's Quilt repo
- Refactor the form to include translations using Quilts react-i18n hook
Read some Shopify Dev Blog articles and come up with a summary.
Polaris is a design system built to help both Shopify and 3rd party developers create a consistent look and feel when building apps for our merchants. One feature of the Polaris design system that we will be working with today is their Component library, which provides the following benefits:
- Components are flexible enough to meet diverse needs
- All Components meet accessibility standards and are responsive to any screen or device
- Shopify provides a well-documented public interface with guidelines and conventions
Let's take a closer look at the Polaris documenation.
Here is the Starter CodeSandbox that we will be working with during this lesson. It contains an existing form which we will rebuild using the Polaris Form Component and supporting Polaris styling layout Components.
Since were going to rebuild the existing form using Shopify Components let's first take a look at the Polaris Form Component.
The documenation provides a CodeSandbox link to begin working with the Component which we can see contains all the functionality required to work with a form, including state and handler methods.
The layout of the form will need some work but for now let's copy/paste this into our exising file and then configure our App to import and render the Component.
Instead of rendering the Form as was displayed in the example we are instead presented with the following error:
The message indicates that our app must be wrapped in an AppProvider Component so let's take a look at the App provider docs and see what this entails.
Looking at the example it's clear the AppProvider wraps the parent level Page Component and is also provided an i18n prop that is passed some configuration options.
We won't concern ourselves just yet with the i18n prop for now, but let's import the provider so we can start working with the Form.
To do this we will import the AppProvider into App and render it as the top level Component.
With our Form in working condition we can start making the edits needed to recreate the existing Form design and functionality.
First let's clean up the form and remove any reference to signing up for the newsletter. This requires we do the following:
- Remove the Checkbox Component and it's corresponding import
- Remote the HandleNewsLetterChange function
- delete setNewsLetter() from the handleSubmit function
- delete the existing state for newsLetter
Now let's setup the form to include the additional functionality needed to support a new password input field which includes the following:
- adding new state values of [password, setPassword]
- add a new handler called handlePasswordChange
- update handleSubmit to clear the password field
- add and configure the TextField Component
Here is what the code required to support our new TextField looks like:
Now let's add the TextField. All we need to do is copy the existing TextField and rename several of the properties. Since were also looking to rebuild the existing form we will need to remove the label and helpText properties and add placeholder text for both inputs.
With all those changes in place our Form should now look like the following:
Although we could always opt to use our own CSS to apply styling there are several additional Shopify Components that we can use to bring us closer to the original design. The ones we will work with now can be found under their corresponding categories:
Button
Lets start with the Button Component since that is a visual part of the form. If we take a look at the docs we can see that we can make use of the following styling options:
- primary - changes the color to green
- fullWidth - expands the button to full width
<Button primary fullWidth>Submit</Button>
If we try submitting the form by clicking on the Submit button we should notice that nothing happens. There is one additional prop we will need to add to the button to trigger this action and that is submit.
Card
It's time to wrap our Form so that it contains the additional white spacing around it and also includes the Login text located at the top of the form.
To do this we will import a Card Component and make use of the following props:
- sectioned - auto wrap content in a section
- title - adds title content for the card
import { Button, Form, FormLayout, TextField, Card } from "@shopify/polaris";
<Card sectioned title="Login">
<Form onSubmit={handleSubmit}>
// ...rest of code
</Card>
Once we add the Card, along with it's props, we can see the title and additional spacing. If we take a look in Dev Tools we can see all the structural Components.
Page
Let's import the Page Component which will wrap the entire Card and include the following property:
- narrowWidth
<Page narrowWidth>
<Card sectioned title="Login">
<Form onSubmit={handleSubmit}>
...rest of code
</Form>
</Card>
</Page>
Here is how the Form should look with all the changes we've made:
Form is coming along and requires only a few more subtle tweaks to make it look like our original design. Two things we still need to implement:
- remove the space between the inputs
- decrease the width
Let's first take a look in Dev Tools and see where the additional spacing is coming from for the input fields and button.
It's clear that the Polaris-FormLayout__Item is where the margin is being assigned. These elements are direct children of the FormLayout Component so if we were to add an extra div around the inputs then that would be become the Polaris-FormLayout__Item and margin would only be applied once.
<FormLayout>
<div>
<TextField
This seems to do the trick. Now all we need to do is add a small bit of custom CSS to decrease the width even more.
If we take a look at Dev Tools we should see the setting for Polaris-Page and if we add the following CSS rule to styles.css our form will be complete.
.Polaris-Page {
max-width: 420px;
}
Since working with Forms is common when working in the Admin, Shopify took it one step further and created an entire React-Form npm package that does much of the heavy lifting. The package is one of many which are included in their Quilt Repo.
Let's take a closer look at the publicly available Quilt repository of packages and then we take a deeper dive into the react-form package.
In order to demo react-form without rebuilding the template, we've incorporated our previous Polaris Form Component along with a few changes. We've also gone ahead and imported react-form as a dependency.
This package exports a variety of hooks for all things form state but the quickest way to get up and running is with the following hooks:
- useField: used for handling the state and validations of an input field.
- useForm : used for handling all state logic
Let's first import the hooks from the react-form library.
import {
useField,
useForm,
} from "@shopify/react-form";
Let's start with useForm as it's used for handling all state logic. There are several fields we can work with but for now let's focus on the following:
- fields: manages the form fields
- submit: replaces the handleSubmit function and handles onSubmit
- reset: rests the form once submitted
useForm requires that we pass it a configuration object that contains a fields key and onSubmit function. Let's also add a console.log so we can confirm when the form has been submitted.
const { fields, submit} = useForm({
fields: {
email: '',
password: ''
},
async onSubmit(form) {
console.log('onSubmit - form', form);
return { status: 'success' };
}
});
We need to replace the existing values with the ones we have just instantiated, which means we need to update the onSubmit event listener and TextField Components.
Lets first add a console log to confirm that fields contains the keys we will need to reference. .
console.log('fields', fields)
fields {email: "", password: ""}
We should be able to reference those values in our TextField Components. Let's first try with email.
<Form onSubmit={submit}>
<FormLayout>
<div>
<TextField placeholder="Email Address" {fields.email} />
</div>
//...additonal code
</FormLayout>
</Form>
Were presented with following error which states it expected "..." as well.
Adding the spread operator will indeed resolve the issue but were not exactly sure at this time as to why it is required. That will be answered in a few more steps.
<TextField placeholder="Email Address" {...fields.email} />
If we type into the fields we will notice that the values aren't being captured. That's because we need the help of the useField hook.
email: useField({
value: "",
}),
password: useField({
value: "",
})
Now if we type into the inputs we can see that the text is being rendered in the UI.
We can also confirm in the console that those values are being captured by the form when we click Submit.
onSubmit - form {email: "joe@gmail.com", password: "password"}
Let's take one final look at the previous console log of fields to see if anything has changed. We should see that each fields is now an object that contains many more values, two of which are providing use the functionality we need are: value and onChange.
When working with a form one thing that we need to do is clear the fields once the Form has been submitted. We can do this by making use of the the reset function from useForm and calling it in onSubmit.
export default function ReactPolarisForm() {
const { fields, submit, reset} = useForm({
fields: {
email: useField({
value: "",
}),
password: useField({
value: "",
})
},
async onSubmit(form) {
console.log('onSubmit - form', form);
reset()
return { status: 'success' };
}
});
Now that we have the basic form functionality working let's add some validation. useForm provides the following validation:
- notEmpty - confirms the field has input
- lengthMoreThan - confirms the field meets a certain length criteria
To make use of these setting we will need to import them from react-form.
import {
useField,
useForm,
notEmpty,
lengthMoreThan
} from '@shopify/react-form';
Applying these validations to useField requires that we add a new validate key and include an array of validations.
fields: {
email: useField({
value: '',
validates: [
notEmpty('field is required'),
lengthMoreThan(3, 'Email must be more than 3 characters')
]
}),
password: useField({
value: '',
validates: [
notEmpty('field is required'),
lengthMoreThan(3, 'Password must be more than 8 characters')
]
})
}
Shopify is an international company with merchants joining the platform from all over the world. In order to accommodate this internationalization Shopify has created a react-i18n library which can be configured to provide a certain level of translations.
Let's take a closer look at the react-i18n library in the Quilt repo.
We've gone ahead and imported the library so we can jump right in.
This library requires a provider component which supplies i18n details to the rest of the app, and coordinates the loading of translations. Somewhere near the "top" of your application, render a I18nContext.Provider component. This component accepts an I18nManager as the value prop, which allows you to specify the global i18n properties.
One thing to note is the I18nContext.Provider will pass context to App so we will opt to configure the provider as the very top of our application in index.js.
Here we will import both I18nContext and I18Manager.
import { I18nContext, I18nManager } from "@shopify/react-i18n";
Then we will need to define our current locale. For the sake of this demo we will hard code it but in a production this would be set dynamically.
import { I18nContext, I18nManager } from "@shopify/react-i18n";
const locale = "es";
const i18nManager = new I18nManager({
locale,
onError(error) {}
});
Let's now enable the i18nContext.Provider and pass the i18Manager.
import { I18nContext, I18nManager } from "@shopify/react-i18n";
const locale = "es";
const i18nManager = new I18nManager({
locale,
onError(error) {}
});
const rootElement = document.getElementById("root");
ReactDOM.render(
<I18nContext.Provider value={i18nManager}>
<App />
</I18nContext.Provider>,
rootElement
);
With our context in place let's configure App to enable translations and pass it down to it's children. This requires that we first import the useI18n hook and define a fallback language.
import { useI18n } from '@shopify/react-i18n';
import en from '../translations/en.json';
Inside the App component lets instantiate the useI18n hook. The hook returns both an i18n value and a ShareTranslations Component. i18n is passed a translation config object with the below options:
const [i18n, ShareTranslations] = useI18n({
id: 'Translations',
fallback: en,
translations(locale) {
return import(`../translations/${locale}.json`);
}
});
The AppProvider is then passed the translations via an i18n prop and ShareTranslations component is then used to share the translations to all child components.
<AppProvider i18n={i18n.translations}>
<ShareTranslations>
<ReactForm />
</ShareTranslations>
</AppProvider>
We must now configure every child Component to consume the translations by importing the i18n hook. Let's go to ReactForm Component to import and instantiate the i18n hook.
import { useI18n } from "@shopify/react-i18n";
export default function ReactPolarisForm() {
const [i18n] = useI18n();
//...rest of code
}
Let's make use of the translations for the Login header and placeholder text in the TextField Components.
<Card sectioned title={i18n.translate('Translations.heading')}>
<TextField
placeholder={i18n.translate('Translations.email')}
{...fields.email}
/>
<TextField
placeholder={i18n.translate('Translations.password')}
{...fields.password}
/>
Here is the Solution CodeSandbox