Seven Months Later – Wayfair Tech Blog


It’s been seven months since Dan Uhl reflected on our conversion to React and Redux at Wayfair. The article was written right after the Product Detail Page (or PDP – you might know it as the page you see when you’re ready to purchase a product here at Wayfair) was converted entirely to React and Redux. It took a process of shaping our data for our new data store and working down the tree of components on our page, converting each one from Wayfair’s Tungsten component framework to React components. The whole project was an exciting success.

Here, seven months later, we are definitely feeling the benefits from the switch. It allowed us to iterate a lot faster, which is an important part of how we deliver new features and experiences to our customers. We iterate on features, then measure the results each time. Ben Clark, Wayfair’s Chief Architect, says that moving fast allows the company to deliver on this by two sets of metrics: “First and foremost, speed to market for new business ideas and features. We also look at performance metrics: web page load times, lead times for delivery, etc., etc. We measure everything.”

This ability to iterate quickly doesn’t come easy: The PDP has multiple teams working on their own customer problems on the page. The Shipping team needs to provide accurate shipping information, the Merchandising team are there to make sure that product information is as clear as possible, the Product Media team makes sure product imagery gives an accurate representation of the product as possible, etc.

All of these teams are continuously deploying updates to their features across a variety of pages, not just the PDP. This team would feel most at ease knowing that everyone can work together in harmony, having the tools to work quicker, easier, and more reliably (using immutable data structures, Jest, and Flow Static Type checking).

Keep reading to see how our conversion to React and Redux has significantly and positively impacted the speed and substance of our work.

React and Redux make Iteration Quicker and Easier

By a few measurements, React/Redux has made it easier for us to iterate faster on customer problems than our previous Tungsten components. For example, by metrics of total page time (a longtime pain point for the PDP), just switching to a fully React/Redux page made load time 2.5 seconds faster. We are constantly monitoring real user metrics about our page, and React allows us to easily pull this data together.

Moving Components Becomes Easy

React is an awesome way to control the user interface, but Redux makes React sing by providing a global state. This makes iterating on a design super speedy, since moving components around is as simple as just moving them from one location to another.

Consider a simple React page where components are nested and data is routed through each component: The problem with an architecture like this is that if someone wants to move something from one part of the tree to another, merge requests become filled with large diffs of data routing being moved between files, or worse, duplicated.

At Wayfair, we solve this problem with the Redux container pattern – where components are simple, only reacting to their props, and aren’t passing data to their children. Data is fetched from the Redux store’s global application state and passed to each component using a higher order “container” component. This means that the data flows from each container to the component, rather than having to send the data up and down the entire component tree.

This also means that moving components around the page or between components can occur via moving them around in the code. Let’s say we wanted to move components from the left hand column to a new “Top Sticky Section” that remains visible when the user scrolls:

Before:

// Original
render() {
  return (
    <div className="product details page">
      <LeftColumn>
        <ProductTitle></ProductTitle>
        <ProductPrice></ProductPrice>
        <ProductImage></ProductImage>
      </LeftColumn>
      <RightColumn>
        <ShippingInput></ShippingInput>
        <QuantityInput></QuantityInput>
        <OptionsInput></OptionsInput>
        <SubmitButton></SubmitButton>
      </RightColumn>
    </div>
  );
}

After:

// Changed - Let’s move products into a top sticky section
render() {
  return (
    <div className="product details page">
      <TopStickySection>
        <ProductTitle></ProductTitle>
        <ProductImage></ProductImage>
      </TopStickySection>
      <LeftColumn>
        <ProductPrice></ProductPrice>
      </LeftColumn>
      <RightColumn>
        <ShippingInput></ShippingInput>
        <QuantityInput></QuantityInput>
        <OptionsInput></OptionsInput>
        <SubmitButton></SubmitButton>
      </RightColumn>
    </div>
  );
}

In this case, no data had to be touched at all, the components just changed order, and the difference above is very small. This keeps merge requests clean and focused on exactly what has occurred (the presentation changed, no data changed). We are currently running a test very similar to the gist above, where we show different layouts to customers on the PDP, trying to understand how to solve problems for them that a layout itself may solve or aggravate. Being able to focus solely on the presentation changes, and not worry about the data or behavior of components means that the merge requests were limited to the changes related to the layouts, rather than the data flow. This lead to more useful code reviews and a quicker time to deployment.

Changing Data Shape Becomes Easy, Too

Additionally, changes to the data are similarly easier with React/Redux. Since the concept of selectors separates the data from the presentational components, changing the shape of the data is simple enough.

We can take a look at how this works via another example. Recently, on the PDP, the Customize and Samples team tackled a user problem that required an important change to the data.

Since a lot of our customers like to see all different visual variations of a product when they are browsing, say, rugs, when they are purchasing a product, they might be intrigued by another pattern that they might not have seen. All of these representations are categorized as separate SKUs (our unique way of defining single items). But to the customer, however, they are just the same rug, shown in different patterns. Why not let them see it that way?

In order to do this, we keep track of each variation as a SKU, which maps to a particular PDP you might land on from browsing through many others. When the user lands there, we’d like to allow them to essentially swap between PDPs very quickly – without knowing that they’re moving from one SKU to another. A first iteration to this problem (before React and Redux) was to send users from one page to another. However, this forces the user to physically reload the page.

In our original implementation, the Redux state only loaded SKU-level information for each SKU one at a time. One page had one SKU’s worth of data. However, since all data access was done with selectors, it was simple to move the SKU data to an array of SKUs (one for each product that we wanted to show as an option), and the entire page could thus change to display information about the new SKU as if they were just another option.

Our old data structure:

{
    sku: 'ABC123',
    price: 133.99,
    shippingDays: 3,
    [...]
}

And the new one:

{
    activeSku: 'ABC123',
    skus: [
        {
            sku: 'ABC123',
            price: 133.99,
            shippingDays: 3,
            [...]
        },
        {
            sku: 'ABC234',
            price: 111.99,
            shippingDays: 3,
            [...]
        },
        {
            sku: 'ABC567',
            price: 90.99,
            shippingDays: 3,
            [...]
        }
    ]
}

Although changing the shape of the data can reveal that selectors are brittle, because the person writing them nests too deeply, this was a rather straightforward problem to solve. All SKU-specific selectors now use a selector such as the following to get their base product data:

selectCurrentPDP = state => state.skus[state.activeSku]

For all intents and purposes, the entire PDP user interface is flexible enough to switch out ALL data. This allows us to solve a number of customer problems.  

Share Components and State Between Pages

At Wayfair, we have patterns for all reusable user interface components, and a whole team that maintains them – pattern libraries are awesome! This means that a button on the PDP should behave the same as it does on the homepage, and as it does at checkout. In React, most components are just a composition of a few pattern components, and software engineers don’t have to worry about creating components like tooltips, maps, videos, etc. Our engineers get to spend their time working on user problems, rather than reinventing the wheel on components we reuse throughout the site.

In addition to our pattern components, all React components within our codebase can be reused anywhere. Even though data may be different between pages, the goal is to build components that can be used and reused as required. For example, the Shipping team can write a zipcode component that is used both on the PDP and checkout. It will behave the same in each place, and the same code does not have to be written or changed in each location. Much like pattern library components, this means that our engineers spend less time having to rebuild their existing components.

With GraphQL and Apollo, entire data structures can be shared between pages, too. If the Browse page requires information from the PDP’s data model, it can be fetched asynchronously, and the same components and Redux containers from the PDP can be used on the Browse page to show product information.

By sharing components, we ensure that they behave the same in every place we use them (which is a problem we used to struggle with). We also reduce the need to write a lot of the code, focusing instead problem solving.

The Pros of Immutable State

Since Redux relies heavily on one source of truth that is not mutated, we can ensure that there are no side effects between components. For every change, there’s a copy of the last state, plus a copy of the new state. The last state does not change, but the new state is different.

How does this work? An engineer gets a list of all the actions that occurred, producing a new state each time, and they can literally time travel to see all changes to the state when trying to debug something. The Redux Dev Tools even write tests for them that prove an action created a new state that was indeed in contrast. All engineers write their code so that state will never be muted, but rather copied, leading to no side effects between the many different features on a page.

The stability we get from this allows for every component we build, for each page, to share the same data store, reducing the amount of data needing to be fetched and serialized.

Jest Unit Testing For The Win

As Dan’s article pointed out, our JavaScript test coverage was around 2% prior to our React/Redux conversion. On the PDP, we have made it our goal to ensure that every feature we release or bug we fix has unit testing associated with it.

Wayfair’s switch to Jest helps us immensely here by making JavaScript unit testing a breeze. It provides a very robust watch tool as well as support for snapshotting data. Engineers also get really useful stack trace printing, powerful filtering of tests (to only run the ones they care about), and mock functions – all out of the box.

Since we test a lot of data with Redux, our team mate Nick Dreckshage has built, and open-sourced, an awesome tool that we use to manage snapshotted data. In doing so, we keep it up to date with the data being returned from our source of truth. This means mock data for our tests can actually come from real production data that a customer would see.

Writing unit tests does increase the amount of code written, but what’s the upside? Jest has now encouraged engineers to write many more tests using real product data, and regressions in function logic are often caught before being deployed.

We Love Flow Static Type Checking

Although not specific to React, JavaScript has a pain point of not having static type checking like other programming languages. Often, type errors that might be approved in large code reviews are only caught when a user starts experiencing them in the browser (if those errors are being logged back to you from their browser in the first place).

We took the time during our switch to React components to incrementally start using Flow static type checking in our JavaScript code via an opt-in basis. This means our engineers would start receiving feedback in their IDE of choice that their code is not type safe, and alert them of any potential errors that could be caused by loosely typed code. This gives Wayfair engineers confidence in the ability to reduce type errors, and much like our decision to change components to React/Redux one component at a time, the type checking is opt-in. New code that is written can have static type checking, while old code that may be stable can wait –  engineers don’t have to rewrite the entire codebase before reaping the benefits of static analysis. Components, reducers, actions, selectors, and containers can all be type safe, on an opt-in basis, preventing type errors from making it to our users.

Conclusion

So here we are, seven months after our conversion to React, and it’s been an enormous achievement at Wayfair. Most teams now write in React/Redux, benefitting from the same wins that the PDPl team has experienced. We can redesign pages around customer problems a lot faster, share components between pages, refactor the shape of our data, and code without fear of mutated state, knowing that our code is unit tested. All of this is happening while there is a static type checker verifying that we won’t have type safety issues in production. We’re excited with our progress so far, and look forward to being able to iterate on more problems for our customers in the future.

If you want to learn more about how we made the jump to React, Dan’s article gives a nice high level overview of the approach.

Do you enjoy using things like React, Redux, and GraphQL to solve complex problems? We’d love to hear from you – let us know what you’re working on below in the comments!



Source link