Tuesday, January 24, 2023

Behind the front-end

 It is April 2021 and amidst a global pandemic, we are about to embark on our greatest venture yet: developing an SEO app for Shopify. For a development department so focused on WordPress for so long, this is both a dream and a nightmare. Starting fresh on a new platform offers endless possibilities, but even more challenges along the way. Now, after months of intense development, Yoast SEO for Shopify has seen the light of day. And I feel obliged to share this technical journey with you. Not because I think it needs explaining, but because I couldn’t be more proud of the giant leap forwards we took with this project. This is a story of how we, from our attics and kitchen tables, brought ‘SEO for everyone’ to Shopify.

Well, actually… This is merely a look behind the front-end of our new Shopify plugin. My name is Nolle, I’m a front-end developer at Yoast and together with the Components squad, I’m to git blame for just that. I want to start by taking you through the main technologies driving the user interface and I want to end with why we didn’t actually build this front-end for Shopify at all. Come again?

Technologies

Our tech stack was primarily determined by what we’re familiar with and what Shopify promotes. You can experiment when you get a clean slate like this. But creating a really robust solution based on past experiences is even better. And that’s exactly what we did, though there are some firsts in the list below.

React

React obviously needs little introduction. It’s already powering most of our highly interactive interfaces in WordPress and for Shopify we took this even further. The front-end is completely written in React and consists of two single page applications. One for general SEO settings and one for optimizing content (more on this separation later). This makes for a user experience that feels much faster and is packed with cool features like optimistic UI, skeleton loaders and live form feedback.

Technically, we’ve moved away completely from class-based components. We’re now committed to a system of smart and dumb functional components. Smart (controller) components are separated from their dumb (view) counterparts so that the latter can be generalized and reused. We’ve also started to follow a ‘hooks over HOCs’ principle. A principle where we favor React hooks over higher-order components (and basically everything else) as much as possible. This all makes for a modern and future-proof React codebase.

Redux and WordPress Data

Like React, Redux is a familiar face here at Yoast. We’ve been using it to manage centralized state in our JavaScript applications for a long time. In our Shopify plugin, we’re using the WordPress Data module to work with Redux because of its familiarity and benefits. It wraps the core Redux API in a thin layer of optimizations like selector memoization and a Redux Saga like system for writing asynchronous actions, called controls.

Next to a nice and nested state structure, we feel like this store has become one of our cleanest yet by adhering to a few simple rules. For instance, we got rid of those dreadful state Booleans (isLoading, isSuccessful, etc.) and replaced them with status constants. That way, the state of something asynchronous for instance can be traced back to one place. Instead of deriving it from multiple Booleans. No more weird edge cases in-between states!

// Replace those bug prone state Booleans
// with status constants.

// Using status booleans in state.
const state = {
  isLoading: true,
  hasError: false,
};

// Using status enums in state.
const state = {
  status: "loading", // || "idle" || "success" || "error"
};

We’ve also tried to be strict in writing actions in an event-like manner. Meaning we don’t ‘set’ anything in the store, we only ‘inform’ it that something (an event) has occurred. For instance, the difference between setActiveItem and itemActivated is subtle. But the latter is not at all coupled to a specific reducer as the former indicates. In a good Redux store, every reducer is able to respond to every action if needed. Again, following simple rules like these has made all the difference for us in creating a robust store.

// Event-like Redux actions promote looser coupling
// between reducers and the actions it responds to.

// Using setter like actions.
const authenticationReducer = ( state, action ) => {
  switch ( action.type ) {
    case "setIsAuthenticated": return {
      ...state,
      isAuthenticated: action.payload.isAuthenticated,
    };

    case "setUsername": return {
      ...state,
      username: action.payload.username,
    };

    default: return state;
  }
};

const notificationReducer = ( state, action ) => {
  switch ( action.type ) {
    case "setAuthenticationNotification": return [
      ...state,
      {
        type: "success",
        message: "You were successfully authenticated!",
      },
    ];

    default: return state;
  }
};

// Using event like actions.
const authenticationReducer = ( state, action ) => {
  switch ( action.type ) {
    case "user/authenticated": return {
      ...state,
      status: "authenticated",
      username: action.payload.username,
    };

    default: return state;
  }
};

// Another reducer reacts to the same action.
const notificationReducer = ( state, action ) => {
  switch ( action.type ) {
    case "user/authenticated": return [
      ...state,
      {
        type: "success",
        message: "You were successfully authenticated!",
      },
    ];

    default: return state;
  }
};

No comments:

Post a Comment