Checklist for a Post-pandemic B2B Surge

Many B2B businesses are already experiencing a post-pandemic surge. Certainly the revenue growth is welcome, but it can bring challenges as well. Here is a checklist to prepare for pent-up demand.

B2B Checklist

Communicate out-of-stocks. Supply chains have not stabilized. Some manufacturers are reverting to domestic suppliers for consistency and reliability.

Surprising a customer with delayed or canceled orders due to out-of-stocks damages the relationship. To overcome:

  • Communicate when an item will be available rather than simply displaying “out of stock.” This sets expectations and helps capture orders from buyers that might look elsewhere.
  • Allow visitors to enter an email address to be automatically notified when the item is back in stock.
  • Consider offering an alternative product in stock.
  • Direct customers to another website where they can buy the product. It builds trust and credibility.

Streamline customer service. Errors happen when a customer service team does not have the information they need or is otherwise inexperienced or overworked. Extended wait times and lack of answers frustrate buyers.

An ecommerce platform can be a terrific customer service tool, lessening the reliance on reps. Modern platforms:

  • Integrate sales with fulfillment. Glitches occur when the orders are not digital, such as a salesperson taking orders over the phone, writing orders on paper, or even receiving faxes. Manual ordering introduces errors.
  • Notify customers of partially filled orders.
  • Support customer service teams who use the website to answer questions and retrieve info quickly. Many manufacturers and distributors find that their site is easier to navigate than internal systems! Customer service personnel can share links with buyers and follow up with resources such as data sheets and safety specs.

Provide self-service ordering.  Relying solely on B2B salespeople to process orders creates bottlenecks and missed opportunities. Salespeople shouldn’t be doing things that customers could do on their own. A good B2B ecommerce site will:

  • Faciliate self-service ordering. Smaller, simpler, and repeat orders don’t typically require personal interaction.
  • Reduce human involvement. For example, software that configures prices enables customers to build their own quotes for a salesperson to review or respond.

Prepare for digital-first B2B. It’s easy to overlook strategic planning. The future of B2B commerce is digital, as Covid-19 has painfully demonstrated. Prepare now for the post-pandemic surge and beyond. Ask yourself:

  • How does the digital transformation impact my business?
  • Which digital features would add value to our customers?
  • How could digital tools strengthen my company’s competitive position and reduce obstacles and threats?

A digital foundation is critical for B2B growth and to test and improve sales and customer service. It enables your company to thrive in the immediate surge and after.

Get Started With React By Building A Whac-A-Mole Game

I’ve been working with React since ~v0.12 was released. (2014! Wow, where did the time go?) It’s changed a lot. I recall certain “Aha” moments along the way. One thing that’s remained is the mindset for using it. We think about things in a different way as opposed to working with the DOM direct.

For me, my learning style is to get something up and running as fast as I can. Then I explore deeper areas of the docs and everything included whenever necessary. Learn by doing, having fun, and pushing things!

Aim

The aim here is to show you enough React to cover some of those “Aha” moments. Leaving you curious enough to dig into things yourself and create your own apps.
I recommend checking out the docs for anything you want to dig into. I won’t be duplicating them.

Please note that you can find all examples in CodePen, but you can also jump to my Github repo for a fully working game.

First App

You can bootstrap a React app in various ways. Below is an example:

import React from 'https://cdn.skypack.dev/react'
import { render } from 'https://cdn.skypack.dev/react-dom' const App = () => <h1>{`Time: ${Date.now()}`}</h1> render(<App/>, document.getElementById('app')

Starting Point

We’ve learned how to make a component and we can roughly gauge what we need.

import React, { Fragment } from 'https://cdn.skypack.dev/react'
import { render } from 'https://cdn.skypack.dev/react-dom' const Moles = ({ children }) => <div>{children}</div>
const Mole = () => <button>Mole</button>
const Timer = () => <div>Time: 00:00</div>
const Score = () => <div>Score: 0</div> const Game = () => ( <Fragment> <h1>Whac-A-Mole</h1> <button>Start/Stop</button> <Score/> <Timer/> <Moles> <Mole/> <Mole/> <Mole/> <Mole/> <Mole/> </Moles> </Fragment>
) render(<Game/>, document.getElementById('app'))

Starting/Stopping

Before we do anything, we need to be able to start and stop the game. Starting the game will trigger elements like the timer and moles to come to life. This is where we can introduce conditional rendering.

const Game = () => { const [playing, setPlaying] = useState(false) return ( <Fragment> {!playing && <h1>Whac-A-Mole</h1>} <button onClick={() => setPlaying(!playing)}> {playing ? 'Stop' : 'Start'} </button> {playing && ( <Fragment> <Score /> <Timer /> <Moles> <Mole /> <Mole /> <Mole /> <Mole /> <Mole /> </Moles> </Fragment> )} </Fragment> )
}

We have a state variable of playing and we use that to render elements that we need. In JSX, we can use a condition with && to render something if the condition is true. Here we say to render the board and its content if we are playing. This also affects the button text where we can use a ternary.

Open the demo at this link and set the extension to highlight renders. Next, you’ll see that the timer renders as time changes, but when we whack a mole, all components re-render.

Loops in JSX

You might be thinking that the way we’re rendering our Moles is inefficient. And you’d be right to think that! There’s an opportunity for us here to render these in a loop.

With JSX, we tend to use Array.map 99% of the time to render a collection of things. For example:

const USERS = [ { id: 1, name: 'Sally' }, { id: 2, name: 'Jack' },
]
const App = () => ( <ul> {USERS.map(({ id, name }) => <li key={id}>{name}</li>)} </ul>
)

The alternative would be to generate the content in a for loop and then render the return from a function.

return ( <ul>{getLoopContent(DATA)}</ul>
)

What’s that key attribute for? That helps React determine what changes need to render. If you can use a unique identifier, then do so! As a last resort, use the index of the item in a collection. (Read the docs on lists for more.)

For our example, we don’t have any data to work with. If you need to generate a collection of things, then here’s a trick you can use:

new Array(NUMBER_OF_THINGS).fill().map()

This could work for you in some scenarios.

return ( <Fragment> <h1>Whac-A-Mole</h1> <button onClick={() => setPlaying(!playing)}>{playing ? 'Stop' : 'Start'}</button> {playing && <Board> <Score value={score} /> <Timer time={TIME_LIMIT} onEnd={() => console.info('Ended')}/> {new Array(5).fill().map((_, id) => <Mole key={id} onWhack={onWhack} /> )} </Board> } </Fragment>
)

Or, if you want a persistent collection, you could use something like uuid:

import { v4 as uuid } from 'https://cdn.skypack.dev/uuid'
const MOLE_COLLECTION = new Array(5).fill().map(() => uuid()) // In our JSX
{MOLE_COLLECTION.map((id) => )}

Ending Game

We can only end our game with the Start button. When we do end it, the score remains when we start again. The onEnd for our Timer also does nothing yet.

We’re going to bring in a third-party solution to make our moles bob up and down. This is an example of how to bring in third-party solutions that work with the DOM. In most cases, we use refs to grab DOM elements, and then we use our solution within an effect.

We’re going to use GreenSock(GSAP) to make our moles bob. We won’t dig into the GSAP APIs today, but if you have any questions about what they’re doing, please ask me!

Here’s an updated Mole with GSAP:

import gsap from 'https://cdn.skypack.dev/gsap' const Mole = ({ onWhack }) => { const buttonRef = useRef(null) useEffect(() => { gsap.set(buttonRef.current, { yPercent: 100 }) gsap.to(buttonRef.current, { yPercent: 0, yoyo: true, repeat: -1, }) }, []) return ( <div className="mole-hole"> <button className="mole" ref={buttonRef} onClick={() => onWhack(MOLE_SCORE)}> Mole </button> </div> )
}

We’ve added a wrapper to the button which allows us to show/hide the Mole, and we’ve also given our button a ref. Using an effect, we can create a tween (GSAP animation) that moves the button up and down.

You’ll also notice that we’re using className which is the attribute equal to class in JSX to apply class names. Why don’t we use the className with GSAP? Because if we have many elements with that className, our effect will try to use them all. This is why useRef is a great choice to stick with.

See the Pen 8. Moving Moles by @jh3y.

Awesome, now we have bobbing Moles, and our game is complete from a functional sense. They all move exactly the same which isn’t ideal. They should operate at different speeds. The points scored should also reduce the longer it takes for a Mole to get whacked.

Our Mole’s internal logic can deal with how scoring and speeds get updated. Passing the initial speed, delay, and points in as props will make for a more flexible component.

<Mole key={index} onWhack={onWhack} points={MOLE_SCORE} delay={0} speed={2} />

Now, for a breakdown of our Mole logic.

Let’s start with how our points will reduce over time. This could be a good candidate for a ref. We have something that doesn’t affect render whose value could get lost in a closure. We create our animation in an effect and it’s never recreated. On each repeat of our animation, we want to decrease the points value by a multiplier. The points value can have a minimum value defined by a pointsMin prop.

const bobRef = useRef(null)
const pointsRef = useRef(points) useEffect(() => { bobRef.current = gsap.to(buttonRef.current, { yPercent: -100, duration: speed, yoyo: true, repeat: -1, delay: delay, repeatDelay: delay, onRepeat: () => { pointsRef.current = Math.floor( Math.max(pointsRef.current * POINTS_MULTIPLIER, pointsMin) ) }, }) return () => { bobRef.current.kill() }
}, [delay, pointsMin, speed])

We’re also creating a ref to keep a reference for our GSAP animation. We will use this when the Mole gets whacked. Note how we also return a function that kills the animation on unmount. If we don’t kill the animation on unmount, the repeat code will keep firing.

See the Pen 9. Score Reduction by @jh3y.

What will happen when a mole gets whacked? We need a new state for that.

const [whacked, setWhacked] = useState(false)

And instead of using the onWhack prop in the onClick of our button, we can create a new function whack. This will set whacked to true and call onWhack with the current pointsRef value.

const whack = () => { setWhacked(true) onWhack(pointsRef.current)
} return ( <div className="mole-hole"> <button className="mole" ref={buttonRef} onClick={whack}> Mole </button> </div>
)

The last thing to do is respond to the whacked state in an effect with useEffect. Using the dependency array, we can make sure we only run the effect when whacked changes. If whacked is true, we reset the points, pause the animation, and animate the Mole underground. Once underground, we wait for a random delay before restarting the animation. The animation will start speedier using timescale and we set whacked back to false.

useEffect(() => { if (whacked) { pointsRef.current = points bobRef.current.pause() gsap.to(buttonRef.current, { yPercent: 100, duration: 0.1, onComplete: () => { gsap.delayedCall(gsap.utils.random(1, 3), () => { setWhacked(false) bobRef.current .restart() .timeScale(bobRef.current.timeScale() * TIME_MULTIPLIER) }) }, }) }
}, [whacked])

That gives us:

See the Pen 10. React to Whacks by @jh3y.

The last thing to do is pass props to our Mole instances that will make them behave differently. But, how we generate these props could cause an issue.

<div className="moles"> {new Array(MOLES).fill().map((_, id) => ( <Mole key={id} onWhack={onWhack} speed={gsap.utils.random(0.5, 1)} delay={gsap.utils.random(0.5, 4)} points={MOLE_SCORE} /> ))}
</div>

This would cause an issue because the props would change on every render as we generate the moles. A better solution could be to generate a new Mole array each time we start the game and iterate over that. This way, we can keep the game random without causing issues.

const generateMoles = () => new Array(MOLES).fill().map(() => ({ speed: gsap.utils.random(0.5, 1), delay: gsap.utils.random(0.5, 4), points: MOLE_SCORE
}))
// Create state for moles
const [moles, setMoles] = useState(generateMoles())
// Update moles on game start
const startGame = () => { setScore(0) setMoles(generateMoles()) setPlaying(true) setFinished(false)
}
// Destructure mole objects as props
<div className="moles"> {moles.map(({speed, delay, points}, id) => ( <Mole key={id} onWhack={onWhack} speed={speed} delay={delay} points={points} /> ))}
</div>

And here’s the result! I’ve gone ahead and added some styling along with a few varieties of moles for our buttons.

See the Pen 11. Functioning Whac-a-Mole by @jh3y.

We now have a fully working “Whac-a-Mole” game built in React. It took us less than 200 lines of code. At this stage, you can take it away and make it your own. Style it how you like, add new features, and so on. Or you can stick around and we can put together some extras!

Tracking The Highest Score

We have a working “Whac-A-Mole”, but how can we keep track of our highest achieved score? We could use an effect to write our score to localStorage every time the game ends. But, what if persisting things was a common need. We could create a custom hook called usePersistentState. This could be a wrapper around useState that reads/writes to localStorage.

 const usePersistentState = (key, initialValue) => { const [state, setState] = useState( window.localStorage.getItem(key) ? JSON.parse(window.localStorage.getItem(key)) : initialValue ) useEffect(() => { window.localStorage.setItem(key, state) }, [key, state]) return [state, setState]
}

And then we can use that in our game:

const [highScore, setHighScore] = usePersistentState('whac-high-score', 0)

We use it exactly the same as useState. And we can hook into onWhack to set a new high score during the game when appropriate:

const endGame = points => { if (score > highScore) setHighScore(score) // play fanfare!
}

How might we be able to tell if our game result is a new high score? Another piece of state? Most likely.

See the Pen 12. Tracking High Score by @jh3y.

Whimsical Touches

At this stage, we’ve covered everything we need to. Even how to make your own custom hook. Feel free to go off and make this your own.

Sticking around? Let’s create another custom hook for adding audio to our game:

const useAudio = (src, volume = 1) => { const [audio, setAudio] = useState(null) useEffect(() => { const AUDIO = new Audio(src) AUDIO.volume = volume setAudio(AUDIO) }, [src]) return { play: () => audio.play(), pause: () => audio.pause(), stop: () => { audio.pause() audio.currentTime = 0 }, }
}

This is a rudimentary hook implementation for playing audio. We provide an audio src and then we get back the API to play it. We can add noise when we “whac” a mole. Then the decision will be, is this part of Mole? Is it something we pass to Mole? Is it something we invoke in onWhack ?

These are the types of decisions that come up in component-driven development. We need to keep portability in mind. Also, what would happen if we wanted to mute the audio? How could we globally do that? It might make more sense as a first approach to control the audio within the Game component:

// Inside Game
const { play: playAudio } = useAudio('/audio/some-audio.mp3')
const onWhack = () => { playAudio() setScore(score + points)
}

It’s all about design and decisions. If we bring in lots of audio, renaming the play variable could get tedious. Returning an Array from our hook-like useState would allow us to name the variable whatever we want. But, it also might be hard to remember which index of the Array accounts for which API method.

See the Pen 13. Squeaky Moles by @jh3y.

That’s It!

More than enough to get you started on your React journey, and we got to make something interesting. We sure did cover a lot:

  • Creating an app,
  • JSX,
  • Components and props,
  • Creating timers,
  • Using refs,
  • Creating custom hooks.

We made a game! And now you can use your new skills to add new features or make it your own.

Where did I take it? At the time of writing, it’s at this stage so far:

See the Pen Whac-a-Mole w/ React && GSAP by @jh3y.

Where To Go Next!

I hope building “Whac-a-Mole” has motivated you to start your React journey. Where next? Well, here are some links to resources to check out if you’re looking to dig in more — some of which are ones I found useful along the way.

The Magic of Fringe Sport? Improving Lives

Peter Keller launched Fringe Sport in 2010 to help folks assemble home-based gyms. He also wanted to make a lot of money. “I was very financially motivated,” he told me.

Then he attended a seminar a few years ago that focused on a company’s purpose, its mission. He spoke with employees and customers. He discovered the real value in Fringe Sport.

“Technically, Fringe Sport is a strength and conditioning equipment company,” he said. “But the improvement in people’s lives, that was the magic. We exist to improve lives through strength.”

Identifying a company’s purpose was one of many topics that he and I addressed recently. Our entire audio conversation is embedded below. The transcript that follows is edited for length and clarity.

Eric Bandholz: Tell us about Fringe Sport.

Peter Keller: I started Fringe Sport because I built an amazing gym in my garage, and it was harder than it needed to be. That was 11 years ago. I dismantled that gym, racked it out, and started selling barbells, kettlebells, weight plates, and similar out of my garage.

We’ve grown from there. We’ve supplied more than 100,000 customers over the years with garage gym gear. We’ve also helped a bunch of entrepreneurs grow thriving community gyms — powerlifting gyms, CrossFit gyms, that sort of thing.

Bandholz: You’ve experienced ups and downs.

Keller: I’ve always shared all of this stuff that I do wrong. So bear with me here for a couple of examples.

First, I founded Fringe with my brother. He was my co-founder. We had one of the dirtiest, worst, most painful business breakups that you could possibly imagine without lawyers getting involved.

I also had an employee steal six figures from me. It smacked us down and made me feel like an idiot. Before that, I’d looked at other people and said, “How stupid. You didn’t notice $100,000 going missing?” Then it happened to me.

Bandholz: Your transparency has helped others, no doubt. I tend to share things too. It’s a form of therapy. I’ve lost five figures, not through theft, but just from incompetent product launches. It’s not fun. What is Fringe about now?

Keller: I’m involved with the Entrepreneurs Organization. I attended an EO program a few years ago from someone who had worked closely with Simon Sinek, the author of the book “Start with Why.” The presentation described a “Find Your Why” framework and workbook. I loved the program, except for that presentation. I was like, “Start with why. That’s millennial bull crap. Why can’t these people just go to work and then go home.”

A few months later, I ran into the fellow who ran that program. I told him I loved everything except the “Find Your Why” stuff. He went full stop, saying, “You are missing something massive here. Buy that book, spend 10 hours on it. If it’s wasted, I’ll apologize to you. But I don’t think it’s going to be wasted.”

So I bought the book, and I started working through it. It talks about helping customers and receiving feedback from them, such as, “Thank you for this.” The book suggests reviewing all of that feedback to find the commonality. Then go to your employees, and ask them about the customers that they’re proud of helping. What are the deals that they care about?

At first, your employees will say, “I sold this guy $10,000 worth of stuff. I made a good commission.” But they’re not really proud of that. Ask them what really filled their heart.

So I asked a bunch of our employees. Their stories coalesced around customers who had come to us, gotten some barbells, weight plates, something like that. Then they came back and said, “Look at the weight that I lost. Look at the competition that I won. My wife was struggling with depression. She started lifting weights, and the depression’s gone.” The stories revolved around lives that improved through strength. Technically, Fringe Sport is a strength and conditioning equipment company. But the improvement in people’s lives, that was the magic.

As part of the process, I traveled throughout the country to where customers were using our products. I went to a ton of garages all over the U.S. I also went to a bunch of CrossFit boxes, a bunch of powerlifting gyms.

I saw our customers. We exist to improve lives through strength.

There’s one other thing on the “Find Your Why” framework. I found my personal why. In the early days of Fringe Sport, the why was not to improve lives through strength. It was so that Peter Keller does not have to get a job. That’s not an acceptable why to anyone except me and, possibly, my wife.

Bandholz: I’m a believer in core values, brand, mission. When you think about building a business, as a new entrepreneur, there’s a lot of glamor. There’s the Gary Vaynerchuk’s, the Tai Lopez’s. There’s seemingly a ton of freedom, private jets, travel.

But the reality is you have to grind, absolutely grind. You have to go through tough relationships with your brother. You have to go through employees stealing hundreds of thousands of dollars. You have to go through problems with your partner or your kids, who ask, “Why didn’t you make it to my event? Or why were you traveling for my birthday.” These are sacrifices you make. I’ve never read “Start with Why.” But it’s something I entirely resonate with.

Beardbrand’s mission is to make men awesome through grooming. We get stories from our customers, which I put in my book of reminders. Becoming mission-oriented allows us to do things beyond merely selling.

Keller: I fumbled around for years until I figured that out. I was very financially motivated when I became an entrepreneur. I had been working for an ecommerce business for 10 years. I had worked my way up in that company. I saw the CEO become very wealthy, which I respected. I thought, “I should be the one who’s getting wealthy.”

But obtaining wealth is such an empty goal because money’s not going to love you in the morning. Your significant other will, your kids will. And your customers will if you do it for the right reasons.

Bandholz: The more value you bring to the world, the more wealth you’ll generate.

Keller: Absolutely.

Bandholz: You’ve found your company’s purpose.

Keller: Yes, but I’m stuck now on leadership and developing employees. I am struggling because we reached another level of scale last year that I’d only dreamt of. My dreams are coming true in terms of our impact on the world. But we are dealing with new problems. I’ve always been a workhorse — to fix things, to solve problems, to develop products. Now, I am suddenly running my largest organization.

We have about 30 employees. The company is successful. We’re helping our customers. But I’m not succeeding at it the way that I need to. I don’t know how to unlock that level of leadership.

Bandholz: You’re a longtime contributor to Practical Ecommerce. Beyond that, where can people learn more about you and Fringe Sport?

Keller: You can find me on Twitter, although I’m not very active, @petekeller. I’m also on Instagram and LinkedIn. Our company is at FringeSport.com.

Incidentally, I have a funny story about oversharing. One year I invited all 40,000 people on our email newsletter list to my house. I didn’t clear it with my wife first.

I said in the email, “I’m having a party on January 1 in Austin, Texas. Anybody who wants to come, just reply to this email, and I’ll send you my address.” Twenty or so people replied, saying, “I’ll be there.” I sent them my address. I then told my wife, “Hey baby, we have some guests coming by.” She hit the roof. Rightly so. I’m an oversharer.