Redux manufacturing: introducing the warehouse floor

This is part two, “Introducing the warehouse floor”, of Redux manufacturing, an introductory series to the JavaScript library Redux. Readers are encouraged to read the first part of this series, “Learning by analogy”, here.

In the first part of this series, we explored why we’ll use analogy to explore Redux. As I mentioned in the previous post, Redux can be really difficult to pick up:

I’ve worked with Redux since 2015, shortly after the first version of it was released. In my experience since then, working on applications of wildly different scale, and with a variety of developers at different experience levels, a single conclusion has come front and center: learning Redux is hard.

I haven’t yet encountered a developer on any software team I’ve worked on that has intuitively picked up Redux. There’s a learning curve––the primary reason, in my opinion, is because it is all new. The concept of data flow is usually handled for you (such as in Ember.js), and managing it yourself and defining such strict logic around it can be really uncomfortable to pick up.

When you do learn Redux, it seems so simple in retrospect: it becomes difficult to remember just why it was so complicated to begin with. This is because while the concepts themselves are straightforward, they’re radically different in many ways from what “conventional” web development has looked like in the past.

Our solution is the analogy: taking what we already know, and remixing it to understand an entirely new concept. This series uses the warehouse floor, where workers come together to build a (hopefully) functioning supply chain to ship products to customers. The warehouse floor is a familiar scenario in project management and “flow” circles, as it has been used in business parables like “The Goal” to explain effective management and process.

The Widget Company

Today, I’ll introduce the players of the warehouse floor. We’ll understand how they work, and more importantly, where they conflict in problematic ways. We’ll also begin to model a React application to show how the different pieces of our warehouse will be represented with data.

Our fictitious company, The Widget Company (“TWC”), which builds trendy “Widgets”, has a few departments–the Orders department collects widget orders from customers, and delivers them to the warehouse.

The warehouse has a number of departments:
Raw materials is an inventory of the components for creating widgets.
Manufacturing takes raw materials and combines them into widgets.
QA, or Quality Assurance, looks at the completed widgets and gives them the “OK”–they’re acceptable to send to customers.
Inventory holds a large number of widgets until they are used as part of an order, as well as packaged items that are ready for shipping.
Packaging takes an order, and prepares widgets by wrapping and boxing them for a customer, before putting them back into inventory.
Shipping takes a packaged order from inventory, and delivers the widgets to a customer.

If it seems like there’s a natural flow to the warehouse, you’re correct. The basic progression of a widget through the warehouse looks like this:

Sometimes things get complicated

While the flow of a widget itself may seem complex, the ordering and delivery process is anything but. As it stands, TWC suffers from a variety of problems:
– Many orders come in when there are no widgets in the Inventory, or when no orders arrive, there are too many widgets–this understocking or overstocking is expensive and time-consuming.
– The Ordering department contacts Packaging directly to begin preparing an order for shipment–if a massive number of new orders come in, Manufacturing is left unaware, leading to the above understock/overstock situation.
– Given an understocking situation, orders are often “rushed”, which causes widgets to skip QA. This leads to customer dissatisfaction and losing money on refunds/exchanges.

Additionally, some departments have too much knowledge of other departments–Inventory holds both assembled and packaged items, causing confusion when Shipping prepares an order. Both QA and Packaging are delivering items to Inventory, and it is hard for Management, which has so far been unrepresented in our diagrams, to understand where and how work is proceeding.

TWC is facing a crisis: due to warehouse inefficiency and cost, they are reaching dangerous financials lows. A once booming widget business is on the brink of ruin. An accurate representation of TWC might be something like this:

Where’s the code?

It’s a dire picture, but what does it have to do with Redux? What does it have to do with React?

We’re going to model TWC with React and Redux. In doing so, we’re going to explore the issues that plague their production flow, and hopefully fix them, using Redux’s inherently smart design.

This application will be written using syntax blocks directly in the post, but as a full application, it will also be available via GitLab. I’ll link to specific Git commits at each code point, but the complete repository is also available at bytesizedxyz/thewidgetcompany.

Note that in this tutorial, we’ll condense our code samples, preferring to just show the most important part of each commit–at any point in the tutorial, you can use Git to get the exact version of the application at that point in time: git checkout.

Using create-react-app, we’ll build a React application that you can run locally, to “create” and “ship” widgets. The README.md file in the above GitLab repo provides instructions on how to do this.

Welcome to The Widget Company

We’ll begin by initializing our application––as I mentioned, create-react-app is a good resource for this. Our first pass at a foundation to build on is simply an App component, which returns the name of our company:

import React, { Component } from 'react';
import "./index.css"
import "./normalize.css"

const Header = () => <h1>The Widget Company</h1>

class App extends Component {
  render() {
    return (
      <div>
        <Header />
      </div>
    );
  }
}

(commit 16c556)

With our application representing the warehouse, we can began to instantiate each part of the warehouse floor as a component. To begin, we’ll need to model our raw material store, which will display the current parts that we have to build from. A “widget” is made of five pieces: two wheels, a dowel, and two screws to attach them all together. Our Materials component will track how many of each piece we have, as well as display them in a basic HTML list:

class Materials extends Component {
  render() {
    const materials = [
      { name: 'Dowel', count: 2 },
      { name: 'Screw', count: 8 },
      { name: 'Wheel', count: 3 }
    ]

    return (
      <div>
        <h2>Raw Materials</h2>
        <ul>
          {materials.map(material => <li>{material.name}: {material.count}</li>)}
        </ul>
      </div>
    );
  }
}

(commit 9edf27)

Thinking ahead, we’ll soon need to pull materials from our raw material store, in order to actually construct widgets. Should we make the materials in our component accessible to the rest of the app?

In short, no. This pattern of sharing state from one component with another, often implemented with React’s “context” feature, is considered a strong antipattern in React: it’s unreliable and is, at this point, strongly discouraged by the React team:

The vast majority of applications do not need to use context.

If you want your application to be stable, don’t use context. It is an experimental API and it is likely to break in future releases of React.

If you aren’t familiar with state management libraries like Redux or MobX, don’t use context. For many practical applications, these libraries and their React bindings are a good choice for managing state that is relevant to many components. It is far more likely that Redux is the right solution to your problem than that context is the right solution.

Instead, we’ll take our materials and bring them into the App component, passing them into Materials and, in the future, whatever other components need access to it. This approach is a common pattern, and foreshadows how we’ll think about our application’s state when we introduce Redux:

// App.js
import Materials from './Materials'

class App extends Component {
  constructor() {
    super()
    this.state = {
      materials: [
        { name: 'Dowel', count: 2 },
        { name: 'Screw', count: 8 },
        { name: 'Wheel', count: 3 }
      ]
    }
  }
  render() {
    const { materials } = this.state
    return (
      <div>
        <Header />
        <Materials materials={materials} />
      </div>
    );
  }
}

// Materials.js

class Materials extends Component {
  render() {
    const { materials } = this.props
    return (
      <div>
        <h2>Raw Materials</h2>
        <ul>
          {materials.map(material => <li>{material.name}: {material.count}</li>)}
        </ul>
      </div>
    );
  }
}

(commit 4c58ad)

With our raw materials available at the App level, we can begin to manufacture widgets. Our Manufacturing component will accept a collection of materials and given that enough of each piece is available, will create a widget.

A few concepts are introduced here. The first is that of “actions”. A HTML button needs to be connected to some action–in React, we often do this by setting the onClick handler for a button. In our Manufacturing component, a manufactureWidget function will check the provided materials: if there’s enough pieces to make a widget, the component calls this.props.addWidget, which creates a widget and adds it to our application state. If there isn’t enough pieces to create a widget, the component calls this.props.presentError with an error message, which is set in state as error and presents as banner text in our application:

// App.js

import Manufacturing from './Manufacturing'

class App extends Component {
  constructor() {
    super()

    this.state = {
      error: null,
      materials: {
        // repeated...
      },
      widgets: []
    }

    this.addWidget = this.addWidget.bind(this)
    this.presentError = this.presentError.bind(this)
  }

  addWidget() {
    const newState = {}
    const newMaterials = this.state.materials
    newMaterials.dowel.count -= 1
    newMaterials.screw.count -= 2
    newMaterials.wheel.count -= 2

    const newWidget = { created: Date.now() }
    const newWidgetsInventory = [].concat(this.state.widgets, newWidget)

    this.setState(Object.assign({}, this.state, {
      materials: newMaterials,
      widgets: newWidgetsInventory
    }))
  }

  presentError(message) {
    this.setState(Object.assign({}, this.state, { error: message }))
  }

  render() {
    const { error, materials, widgets } = this.state
    return (
      <div>
        <Header />
        {error ? <Error message={error} /> : null}
        <Materials materials={materials} />
        <Manufacturing
          addWidget={this.addWidget}
          materials={materials}
          presentError={this.presentError}
        />
      </div>
    );
  }
}

// Manufacturing.js

class Manufacturing extends Component {
  constructor() {
    super()
    this.manufactureWidget = this.manufactureWidget.bind(this)
  }

  manufactureWidget() {
    const { addWidget, materials, presentError } = this.props
    if (materials.dowel.count >= 1 && materials.screw.count >= 2 && materials.wheel.count >= 2) {
      addWidget()
    } else {
      presentError("Not enough materials to create a widget")
    }
  }

  render() {
    return (
      <div>
        <h2>Manufacturing</h2>
        <button 
          onClick={this.manufactureWidget}>
          Manufacture widget
        </button>
      </div>
    );
  }
}

(commit 47b6c1)

There’s a few design choices here that are intentional, but might be missed if you’re a newer React developer. The first is understanding when and where actions occur. The Manufacturing component, responsible for creating widgets, checks the provided materials and calls the action provided to it for creating actions at the top level. The addWidget action in the App component is responsible for actually modifying the state to add a new widget; in doing so, it subtracts pieces from our raw materials and appends a new widget to our application state.

Our action functions live in the App component to give Manufacturing as little information about the actual process of modifying application state as possible. Arguably, even the knowledge about what is needed to create a widget–two wheels, two screws, and a dowel–is too much for the Manufacturing component to know, but in this case, it gives the component something to do. We could, as an exercise, move manufactureWidget into the App component, and pass manufactureWidget as a prop into Manufacturing, instead of appWidget and presentError. For now, this solution works, and also is a precursor to how we’ll think about Redux’s actions, which live outside of React components, and are passed as props into our application.

We’re creating widgets, but we have no way of tracking how many widgets we’ve produced. Of course, we need to define an Inventory component. Unlike our last complicated set of changes, this component accepts widgets from application state, and displays the current number of widgets in inventory:

// App.js

import Inventory from './Inventory'

class App extends Component {
  render() {
    const { error, materials, widgets } = this.state
    return (
      <div>
        // ...
        <Inventory widgets={widgets} />
      </div>
    );
  }
}

// Inventory.js

class Inventory extends Component {
  render() {
    const { widgets } = this.props
    return (
      <div>
        <h2>Inventory</h2>
        <h4>
          {widgets.length} widgets in inventory
        </h4>
      </div>
    );
  }
}

(commit eaa849)

If you remember our initial diagram of the warehouse, you might remember that there is a QA (Quality Assurance) component missing here. To model a QA department, we’ll define an error rate–we’ll say 30%, in our example–and randomize a “check” that must pass for widgets to be created. This function, called qaCheck, lives in the App component, so that it can be called during the addWidget process. When it returns false, and a QA check has failed, we increment failed in our application state, which is counted by the QA component:

// App.js

import QA from './QA'

class App extends Component {
  qaCheck() {
    const check = Math.floor(Math.random() * 10) + 1
    return check < 7
  }

  addWidget() {
    const newMaterials = this.state.materials
    newMaterials.dowel.count -= 1
    newMaterials.screw.count -= 2
    newMaterials.wheel.count -= 2

    let newState = { materials: newMaterials }

    if (this.qaCheck()) {
      const newWidget = { created: Date.now() }
      const newWidgetsInventory = [].concat(this.state.widgets, newWidget)

      newState.widgets = newWidgetsInventory
    } else {
      newState.failed = this.state.failed += 1
      newState.error = "A widget failed QA!"
    }

    this.setState(Object.assign({}, this.state, newState))
  }

  render() {
    const { error, failed, materials, widgets } = this.state
    return (
      <div>
        // ...
        <QA failed={failed} />
        // ...
      </div>
    );
  }
}

// QA.js

class QA extends Component {
  render() {
    const { failed } = this.props
    return (
      <div>
        <h2>QA</h2>
        <h4>{failed} widgets failed QA</h4>
      </div>
    );
  }
}

Note that in the below commit, there’s a small error: the check variable in the qaCheck function, in the below commit, produces numbers between 0 and 9. Checking for greater than 7 when numbers are between 0 and 9 is a 20% error rate, not a 30%, as specified in the requirements. This has been fixed with the addition of + 1 to the end of our check variable, seen in the above code snippet.

(commit 40aaf8)

Our QA department now intercepts the creation of widgets by Manufacturing, and in doing so, provides an opportunity to see how we could deal with “invalid” data in a more complex React application. Given something like these failed pieces of data, we may want to institute some sort of edit-and-retry functionality in a more complex application. By separating failed data into its own part of state, we can easily pass it into an Edit component to allow repair of the data.

At this point, we’ve modeled the first half of our warehouse. We can create widgets and store them in our inventory. In the next part of this series, we’ll look at the ordering process. We’ll allow generation of orders, as well as packaging and shipping orders. We’ll also begin to more carefully examine how we’ve designed our application state, and in doing so, introduce the beginning concepts of Redux to our application.

Bytesized offers technical training for companies. If you or your company want to take a deeper dive into React.js or Redux, including hands-on workshops, lectures, and take-home exercises, contact us to schedule training. We’re excited to work with you.

Read the next post in this series, “Managing state and inventory”.

Leave a Reply

Your email address will not be published. Required fields are marked *