One of the ways that 3rd-party developers can extend the Wix ecosystem is by creating apps that appear in site owners' dashboards. These apps can work with data from the sites they’re installed on, such as Content Management System (CMS) data and business solution data like CRM.
This tutorial explains how to create a React app similar to our Custom Products Catalog template where users can add or remove products from a Wix Stores products catalog through a dashboard page.
We use the following technologies to create the app:
The end result will look like this:
We use the following steps to build the “Custom Products Catalog” app:
Before getting started, make sure that:
We also recommend that you check out the Patterns documentation. Patterns is a complex React library. Familiarizing yourself with the basics will help you better understand the code in this tutorial.
We use the Wix CLI to initialize our "Custom Product Catalog" app. In the process of initializing our app, the Wix CLI automatically:
src
folder containing initial boilerplate code for an app with a dashboard page.package.json
file containing your app dependencies.To initialize the app:
Open a terminal and navigate to the folder where you want to create your app.
Run the following command:
Select Create a new Wix App.
Select Create a basic app.
Enter a name for your app. Let’s name our app Custom Products Catalog.
Back in the terminal, press Enter to select the default folder name (custom-products-catalog
) for your app in your local file system.
You now have a new app in the Custom Apps page, a new folder in your local file system, and a local Git repository for developing and testing your app.
Now that you’ve initialized your app, you can run a local development server to see the app in action, and view local changes as you develop your app. The local development environment runs the app and its initial boilerplate code on the development site that you chose in the previous step.
To run a local development server for your app:
Navigate to your newly-created folder for your app.
Run the following command:
The CLI prompts you to choose a development site (test site), which you’ll use throughout this tutorial to run and test your app. You can choose an existing Wix site as your development site, or create a new one. Let’s Create a new Development Site. The newly created development site is automatically named something like Dev Site 5x2043, and can be found in your Wix account’s list of sites.
Follow the prompt to open the development site on your default browser. If the browser doesn’t open, install your app on your test site manually and skip the next step.
Click Agree & Add to install your app on your development site.
Press 1 to open the local environment in your browser. Your development site’s dashboard opens, and you can see your newly-created app’s dashboard page in the left sidebar. We add the content of our app’s dashboard page in the next step.
Your app is now running on your development site. As you develop your app, any changes made to your app’s code are reflected in real time on your development site.
If your changes don’t show up, try refreshing the page, or closing and reopening the development site.
In this step, we'll add permissions for the app. Every SDK API requires specific permissions to use. In this app, we will use the queryProducts()
, createProduct()
, and deleteProduct()
functions in the Wix SDK’s Stores Products API to get a list of all products, and to add and delete them.
To use these functions, we need to give our app permission requirements in the App Dashboard. Once we do this, anyone installing the app will be prompted to grant the specified permissions.
You can find the permission scopes you need for each API in the individual function’s page in the JavaScript SDK documentation.
Let’s first look for the permissions that we need in the Permission Scopes section of the queryProducts()
function’s page.
The Permission Scopes section indicates that our app must have one of the permission scopes on the list to call the function. Let’s choose the Manage Stores - All Permissions permission scope. It works with createProduct()
and deleteProduct()
as well.
To add permission requirements for the app:
Our app integrates with Wix Stores, so we need to set up Wix Stores on our site, then reinstall the app so that our site request the required permissions:
Note: Make sure to add Wix Stores in both the Dashboard and the Editor.
Note: You'll be prompted to create an extension before testing the app. Ignore this prompt and click Test Your App.
Before we start coding our app, we need to install some npm packages. In your terminal, run the following commands:
The purpose of each of these packages will become clear as we progress.
It’s finally time to start writing some code!
Our plan in this tutorial is to create a React component that defines our dashboard page. We’re going to wrap this component with providers that will manage our data fetching and provide Wix styling for our dashboard page component.
To do this, we’ll create a higher-order component that will accept our dashboard page component and return it wrapped in the necessary providers.
To create the higher-order component:
Create a new file in your app's repo under src > dashboard named withProviders.tsx.
Import the react library, and then WixDesignSystemProvider
:
WixDesignSystemProvider
provides styling for Wix Design System components.
Import WixPatternsProvider
:
WixPatternsProvider
provides styling and data manipulation for Wix Patterns components.
Import withDashboard
:
withDashboard()
is a higher-order component that is temporarily required for use in Wix Patterns. It will continue to be supported while it is required.
We'll wrap our dashboard page with this component.
Write a function named withProviders
to wrap our dashboard page component in WixPatternsProvider
, WixDesignSystemProvider
, and withDashboard()
.
Your complete withProviders.tsx file should look like this:
Our dashboard page component will need to make SDK calls to create and delete Wix Stores product data using React hooks. To simplify our component code, we define these in a separate TypeScript file.
To create your app’s React hooks:
Create a new folder in your app's repo under src > dashboard named hooks.
Create a new file in src > dashboard > hooks named stores.ts.
Import products
from Wix Stores so we can use the Wix Stores Products API:
Import useCallback
:
useCallback
is a react hook that caches a callback function, returning a memorized version of the function that changes only if one of the dependencies has changed.
We use this hook to define how a new product is created and how a product is deleted using the optimisticActions
prop and createProduct
/deleteProduct
from products
.
Whenever any of these dependencies change, useCallback
redefines the callback function.
Import CollectionOptimisticActions
:
CollectionOptimisticActions
is a class that provides a set of utilities for managing optimistic actions on a Wix Collection. It allows us to perform various actions, such as adding, updating, and deleting items from a collection while assuming that these actions will succeed, even before confirming with the server. Learn more about the CollectionOptimisticActions class.
Create a function to create new products named useCreateProduct
:
Inside this function:
Use products
to get the createProduct()
function.
Use useCallback()
to manage our createProduct()
call:
Let's break down the above code:
optimisticActions.createOne()
to create the new product.
submit
function to handle product creation.useCallback()
.Your function should look like this:
Create a function to delete products named useDeleteProducts()
:
Inside this function:
Use products
to get the deleteProduct()
function.
Use useCallback()
to manage our deleteProduct()
calls:
Let's break down the above code:
optimisticActions.deleteMany()
to delete the products.
deleteProduct()
for each one.useCallback()
.Your function should look like this:
Your complete stores.ts file should look like this:
We need a modal where the user can enter the name of the new product and confirm or cancel. This modal will be opened using a button on the dashboard page. To build this modal:
Create a new folder in your app's repo under src > dashboard named components.
Create a new file in src > dashboard > components named create-product.tsx.
Import:
React
, useEffect
, and useState
.Modal
component.Create a CreateProductModal
component to define the appearance and functionality of the modal. Use the following code:
Let’s break down the above code:
useEffect()
to ensure that when the showModal
prop updates, the shown
state variable aligns with it.productName
.FormField
component that takes an input for the product name.Your complete create-product.tsx file should look like this:
Finally, we have all the pieces in place to set up our dashboard page.
But first, let's rename our page to something more appropriate:
"title"
to "Custom Products Catalog"
.Now, let's create the dashboard page itself:
Open the page.tsx file.
Delete all the contents - we're starting from scratch.
Add the following import statements:
This file is where we start to use Patterns.
Patterns is a React library with advanced components that extend the functionality of the core UI React components from the Wix Design System. It simplifies Wix app development by enabling you to easily and consistently implement complex functionalities like querying, displaying, and filtering collection data from remote servers.
In our code, we use Patterns components to create a table of products that you can query, filter, and sort. The table also has functionality to create new products, delete existing products, and choose which columns to display.
Import everything we set up in the previous sections:
Define types for filtering the table's data:
These are the product properties by which the table can be filtered. We'll pass this to the useTableCollection
hook later.
Define supported fields for sorting the table's data:
This line creates a type which we use later to sort fields in ascending or descending order.
For a full understanding, let's break this line down:
Parameters<products.ProductsQueryBuilder['ascending']>
: This part accesses the type definition of the ascending method of the products.ProductsQueryBuilder
object. The Parameters
type extracts the parameter types of a function or method. In this case, it retrieves the parameter types of the ascending method.[0]
: This part accesses the first parameter type of the ascending method. Since the ascending method is expected to take only one parameter (the field name to sort by), accessing the first element effectively gives us the field type that can be used for sorting in ascending order.Parameters<products.ProductsQueryBuilder['descending']>[0]
: This does the same thing for the descending method of the products.ProductsQueryBuilder
object.SupportedQueryFields
: Combining the types extracted from the ascending and descending methods using the OR
operator |
, the SupportedQueryFields
type represents the set of field types that can be used for sorting in the products query builder. This ensures that only valid field types can be used for sorting operations.Map product types to strings so that they display in a readable way in the table:
Products()
componentIt's finally time to make our dashboard page component. We will:
Create a function for our dashboard page component named Products
:
Export the component wrapped by withProviders()
:
The rest of the code on this page will be written inside the Products()
function.
Initialize the shown
state variable to manage the visibility of the modal we created earlier.
Use products
to get the queryProducts()
and deleteProduct()
functions.
Create the tableState
using useTableCollection
.
useTableCollection
is a hook that returns a tableState
object. This is where we define:
Let’s break down the above code:
query
parameter. query
is an object that is passed to the fetchData
function. It contains all the information needed to construct the query builder.queryProducts()
function that we retrieved from the products module to create the query builder using limit
and offset
values.search
is included in the query.filters
is included in the query. Use the properties of filters
to check if a filter has been applied for Type or Last Updated, which we defined earlier in the TableFilters
type.sort
is included in the query.lastUpdated
filter uses the dateRangeFilter
factory.productType
filter uses the stringsArrayFilter
factory.Create the optimisticActions
class using useOptimisticActions
.
useOptimisticActions
is a hook that returns a CollectionOptimisticActions
class. The code below defines logic that anticipates the results of the query that we defined in the previous step. When there is a change that sends a new query to the server (for example, a new filter), this logic will be applied to the data already in the page's memory while we're waiting for the server's response.
Let’s break down the above code:
tableState
above. It returns a function that returns true
if the product would pass the filters, and false
if it wouldn't.
false
if the product wouldn't pass the search
filter.false
if the product wouldn't pass the productType
filter.false
if the product wouldn't pass the lastUpdated
filter.true
if the product would pass all of the filters.Define the createProduct()
and deleteProducts()
functions using the hooks we created in Step 7:
At this stage, your Products()
function should look like this:
We define our page as follows:
Define the CollectionPage
component that will wrap all our components on this page:
Add a CollectionPage.Header
component that contains the page title, breadcrumbs, and a button that opens our Add Product modal:
Add a CollectionPage.Content
component below the CollectionPage.Header
. This will wrap the rest of the components on this page.
Add the CreateProductModal
component that we created in Step 8:
Add a Table
component.
The following code displays a table containing our columns and fetched data. When no items are selected, there is also a search bar, a button that opens an accordion to customize the columns displayed, and a button that opens an accordion to filter the data. When at least one product is selected, the table displays the number of selected products, the maximum number of products that can be selected, a Select All option, and a Delete button.
Let's identify where some of our key functionality is implemented in the above code. For more information on all the Table
props, see Patterns Table Component.
CollectionToolbarFilters
component to display an accordion with the filtering options.
RadioCollectionFilter
defines the way the filter options are displayed in the accordion.DateRangeFilter
defines the way the filter options are displayed in the accordion.MultiBulkActionToolbar
component to create the button in the toolbar that deletes all selected products. When the button is clicked, the user is asked to confirm their decision in a modal (line 30). If the button is disabled, a message is displayed explaining why (lines 26-28).Image
component from the Wix Design System and feed it the image URL from the product object's nested media.mainMedia.image.url
field.Box
for layout with direction set to vertical
and gap to 3px
, indicating vertical stacking with a gap of 3px. Inside this box, add two Text
components for the product's name and description.productTypeToDisplayName
which we defined earlier. If there is no product type, return an empty string.Your page structure code should look like this:
Your complete code should look like this:
Now that the app’s code is ready, you can test it locally using the Wix CLI.
To test your app, do the following:
Run a local development server for your app using the npm run dev
command in your terminal. Your app’s dashboard page will now look like this:
Click the Add Product button and use the modal to add a product.
Select some products and click the Delete button.
If things don’t look right, open your browser’s developer tools and check for errors in the console.
You have now fully developed an app that allows users to add or remove products from a Wix Stores products catalog through a dashboard page.
After testing your app and seeing that it works as expected, you can build and deploy your app.