All new in 2019

04/04/2019

We spent 18 months building the foundation for Engage by Shopgate and, at the beginning of last year, we took a step back to reflect on what we had achieved. We took a deep dive into our architecture and carefully considered feedback from our clients and partners.

With the help of this feedback, we revisited some of our earlier work and focused on streamlining key internal functionalities and improving performance and extensibility.

How big are the changes we made?

We’re committed to minimizing changes to existing APIs to prevent burdening our clients and partners with refactorings. With this in mind, we simplified our feature code and eliminated outdated third-party tools.

What exactly has changed?

Proactive vs. Reactive

One of the biggest improvements is how navigation works inside the app. In Shopgate’s Engage versions 5 and below, the navigation is proactive, meaning the developer can only apply business logic to a navigation action after it happens. As of version 6, the navigation is reactive. This means that the developer can intercept a navigation action and override it before it happens.

Better and faster routing

We converted the frontend router from a custom implementation to Conductor, a third-party module that provides all the features necessary to deliver a powerful, blazing fast React application.

React update

Additionally, we updated React to its newest version, 16.6.0, which provides powerful new ways to optimize our application.The new Context API that React uses optimizes flexibility when accessing data from a deeply nested child in the tree. This upgrade results in the following improvements.

Eliminating unwanted reducers

Previously, we stored UI-related information in our frontend store to avoid passing React props down the tree. Additionally, we built our custom router implementation on top of React and Redux. This construction limited overall flexibility. After changing the router implementation, we could finally remove some unwanted reducers:

  • history (has been completely replaced by the router itself. Redirect information is now part of the state object in every route)
  • navigator (redundant after our refactorings)
  • ui (redundant after our refactorings in favor of React’s context API)
  • view (redundant after our refactorings)

Navigation

Navigation between pages

The application’s internal navigation processes have changed slightly. The primary navigation process using the <Link> component is still the same, but other processes, like redirecting or authentication-protected navigation, have changed.

Relocated React component

We relocated the <Link> component that renders links inside React. Before, it was located with our custom router implementation at @shopgate/pwa-common/components/Router/components/Link. Since we removed the custom router, we moved the component to the common components, under @shopgate/pwa-common/components/Link.

Navigate programmatically

The primary way to navigate programmatically is to dispatch a Redux action. This triggers a specific RxJS subscription, which handles the navigation action and then performs the router action.

The available actions are: historyPush, historyPop and historyReplace. Here is an example of how one of these actions can be used:

import { historyPush } from '@shopgate/pwa-common/actions/router';

// ...
dispatch(historyPush({ pathname: '/cart' }));
// ...

This method can be used inside a connector or in any RxJS subscription.

Redirects

Redirects no longer use the store. Instead, a collection is used to set or unset the redirects as needed.

import { redirects } from '@shopgate/pwa-common/collections';

// Redirect the checkout pathname to the legacy pathname.
redirects.set('/checkout', '/checkout_legacy');
 
// Remove the redirect.
redirects.unset('/checkout');

This redirect registrations can be performed at any place in your code. We usually hook them up in a subscription to the appWillStart$ stream.

Authentication

Authentication is no longer handled by React components and is also no longer bound to protect routes only by the login route.

Similar to the redirects, we already provide a collection to register authentication protected routes.

import { authRoutes } from '@shopgate/pwa-common/collections';

authRoutes('/checkout', '/login');

Before the router opens the /checkout route, the application checks if the user is logged in. If that is not the case, the router opens the /login route and stores the user’s original target location until after the login completes.

NOTE: The developer decides when and how to open the original target location. Sometimes, one has to wait for a server response or wait for a user process to finish. This can’t be automated.

React Components

Any component now has access to the Route information. This information is accessible through the new Context API, introduced in React v16.6.0.

import { RouteContext } from '@shopgate/pwa-common/context';
 
function MyComponent(props, context) {
  // We now know if this component is on the currently visible page!
  const { visible } = context;
   
  return ...;
}
 
MyComponent.contextType = RouteContext;
 
export default MyComponent;

NOTE: This example uses the new contextType property. You can, of course, use an HOC to inject the context properties, via props, into a component. The React Documentation describes how to achieve this.

Router Helpers

It is also possible to directly ask the router for information regarding the history stack.

import { getCurrentRoute } from '@shopgate/pwa-common/helpers/router';

Streams

Routing streams are now much simpler to use and construct. The available streams can be found at @shopgate/pwa-common/streams as described in our Reference Documentation. Here is an example of how to create a new stream:

import { routeDidEnter$ } from '@shopgate/pwa-common/streams';
 
export const categoryDidEnter$ = routeDidEnter$
  .filter(({ action }) => action.route.pattern === '/category/:categoryId');

Components

Some of our component implementations have been simplified or completely reworked. The most important changes are discussed below.

NavDrawer (open and close)

Before our refactorings, it was a real challenge to open and close the Android style Navigation Drawer (a.k.a <NavDrawer>). We changed that by introducing a static API into the component that you can use to open and close the component:

// First you want to import the component at any 
// place in your code where you need to open or close it.
import { NavDrawer } from '@shopgate/pwa-ui-material';

// Then you call the static .open() function to open it ..
NavDrawer.open();

// ... or the static .close() function to close it.
NavDrawer.close();

LoadingProvider

There are many reasons to show a loading indicator on your page. For example, when the user puts a product into the cart.

In version 5 and below, the Product Page shows a loading state while the cart updates in the background. This is not a good approach because it blocks the user from interacting with the UI.

A better approach is to put a loading state on the Cart Page, so that when the user adds a product to the cart and immediately switches routes, only the cart page is blocked until it updates.

This approach is now possible with version 6. We introduced the LoadingProvider, which allows you to set a specific route as loading via the route’s pathname. Here is an example:

import { LoadingProvider } from '@shopgate/pwa-common/providers';

// This will set the /cart route as loading.
LoadingProvider.setLoading('/cart');

// This will unset the /cart route loading state.
LoadingProvider.unsetLoading('/cart');

AppBar

The <AppBar> component is a replacement for the <Navigator> component. The main difference is that it is not a global component inside the theme anymore. Now, every Route has to render its own AppBar.

Every theme comes with several presets that you can use in your custom Route:

<DefaultBar />
<BackBar />
<CloseBar />
<SimpleBar />

Next, let’s explore how the AppBar can be accessed from an extension.

Extensions

Creating new Routes

The process of creating new Routes has been simplified. With the previous described Context API and our pre-defined Theme context it is now super easy to define custom Routes from an extension:

import React from 'react';
import { Route } from '@shopgate/pwa-common/components';
import { Theme } from '@shopgate/pwa-common/context';

function MyProfile() {
  return (
    <Theme>
      {({ View, AppBar }) => (
        <View>
          <AppBar />
          ...
        </View>
      )}
    </Theme>
  );
}
 
function MyProfileRoute() {
  return (
    <Route pattern={USER_PROFILE_PATH} component={MyProfile} />
  );
}
 
export default MyProfileRoute;

We provide a complete guide on creating custom routes in our Developer Documentation.


Author: Florian Mueller, Head of Frontend Development