The view from my Airbnb in Amsterdam ?

Introduction

For months I had been looking forward React Amsterdam. With 1500+ attendees, a killer lineup, and all in an amazing city to boot, what’s not to be excited about?

I flew in from warm, warm (think up to 100 degrees in the middle of the day) southern Vietnam to the north of the Netherlands, where it was in the 30s in the morning ?. Silly me for thinking it would be like April in New York ?.

On the agenda for my first full day there was Kent Dodd’s Advanced React Patterns. To be upfront here, this was definitely the thing I was most looking forward to. Conferences are great, of course, but we have the great wonders of technology and can watch all of the replays on YouTube. ? I wanted to get my hands dirty and be able to code while learning from one of the best in an intensive all-day seminar.

It lived up to my expectations. We were crammed tight in a repurposed restaurant space, which was interesting, but also quite conducive to learning. Being so close to other people encouraged us to collaborate and sometimes commiserate during some of the challenging exercises.

Workshop Overview

Before going into a more detailed summary of some of the patterns taught in the workshop, it’s worth it to provide a general overview of the workshop. To keep the focus on the patterns, we (over-)engineered various iterations of a toggle component. While in a real-world scenario, applying such advanced patterns on a toggle would simply be overkill, I found it useful to allow us to really dig in to the concepts behind these patterns without a mess of components getting in our way.

As mentioned, I was quite happy with the workshop. A problem that I sometimes have with these kinds of workshops is that they can tend to be “all levels,” which in practice means that they have to cater to beginners which then ends up holding back the workshop. This is a problem I’ve encountered particularly with free workshops and sessions you can find on sites like Meetup.com.*

If anything, this workshop had the opposite problem. The workshop assumed knowledge of React going into it and some exposure to React Hooks. As someone who eagerly awaited the day Hooks went stable, this was no problem. There were a few people who struggled a bit to keep up, but again this was an “advanced” React workshop. I would be happy attending more workshops like this.

The format was also nice, as it allowed us to keep our fingers warm by typing most of the time. There were no lectures or anything like that. Just a brief introduction to each exercise, coding time, and going through the solutions. I find this to be the best way to learn and retain information, so kudos to Kent!

Another thing I liked was that the workshop served as a useful overview of what React has to offer in 2019. We went deep into manipulating props in interesting ways, receiving context ?, and useReducer!

Now, I will go over some of the patterns that we went over in the workshop. Note that all of examples are taken from Kent’s Advanced React Patterns workshop exercises, which are available publicly here. I encourage everyone to go through the exercises themselves! Also, like the workshop, I will assume you understand React here, including how hooks and context work.


That’s some intense focus right there!

Advanced React Patterns Examples

Flexible Compound Components with Context

This is actually an enhancement of a regular compound component, with some extra magic ?. What is a compound component? It’s a component family in which the parents and children have semantic meaning. This allows the components themselves to hold and act on implicit state without the consumer having to implement it manually. Still sound confusing? There are actually examples of this with native HTML elements.

A <select> element, for instance, works hand in hand with <option> elements as children. The <select> element knows which option has been clicked without it being programmed manually. We are carrying over this concept from HTML.

Here is how we want our first toggle to be used.

function Usage() {
  return (
    <div>
      <Toggle onToggle={(...args) => console.info("onToggle", ...args)}>
        <Toggle.On>The button is on</Toggle.On>
        <Toggle.Off>The button is off</Toggle.Off>
        <div>
          <Toggle.Button />
        </div>
      </Toggle>
    </div>
  );
}

Note that our Toggle.Button is nested inside a div. This is where the “flexible” comes in! In a real-world use case, we may want to wrap our button in a div for styling purposes. But when we do this, we now lose the ability to rely on something like React.Children when designing our toggle, as that only applies to direct children.

Lucky for us, it’s 2019 and we have a built-in way of sharing state throughout our app. Yes, that’s right, it’s time to welcome back our friend context! ??. Our first step is to use React.createContext to add our Toggle Context.

const ToggleContext = React.createContext()

With our context created, we can provide that context to all of the children of the Toggle component.

function Toggle({ onToggle, children }) {
  const [on, setOn] = React.useState(false);

  const toggle = React.useCallback(() => {
    const newOn = !on;
    setOn(newOn);
    onToggle(newOn);
  });

  return <ToggleContext.Provider value={{ on, toggle }}>{children}</ToggleContext.Provider>;
}

With this, we have already ensured that we have a way for all children (not just direct ones) of ToggleContext.Provider to make use of our on value and toggle function.

function useToggle() {
  const context = useContext(ToggleContext);
  if (!context) {
    throw new Error("Toggle compound components must be rendered within the Toggle component");
  }
  return context;
}

The useToggle custom hook uses our Toggle Context, but also checks that it exists. The only case in which it can be undefined is if the hook has been used not as a descendant of our Toggle component, since that is where the Provider is placed.

With this, we can code the Toggle family child components, comfortable that that useToggle will give us the context values we are looking for.

Toggle.On = function On({ children }) {
  const { on } = useToggle();
  return on ? children : null;
};

Toggle.Off = function Off({ children }) {
  const { on } = useToggle();
  return on ? null : children;
};

Toggle.Button = function Button(props) {
  const { on, toggle } = useToggle();
  return <Switch on={on} onClick={toggle} {...props} />;
};

It’s worth noticing that for Toggle.On and Toggle.Off, whether we return the children depends on the on state. If not on, Toggle.On will not display anything and vice-versa for Toggle.Off. The children in both cases is simply the text context.

Though we are using it for a simple toggle here, the potential applications are endless. This can be applied to a form group with some sort of validation state that only displays given certain conditions, for example. Button groups would also work.

Prop Collections with Getters

Sometimes we want a hook to provide us with common props so that they can be “spread” to other components, while also allowing for the addition of custom behavior to these props. Here is the usage:

function Usage() {
  const { on, getTogglerProps } = useToggle({
    onToggle: (...args) => console.info("onToggle", ...args),
  });
  return (
    <div>
      <Switch {...getTogglerProps({ on })} />
      <hr />
      <button
        {...getTogglerProps({
          "aria-label": "custom-button",
          onClick: () => console.info("onButtonClick"),
          id: "custom-button-id",
        })}
      >
        {on ? "on" : "off"}
      </button>
    </div>
  );
}

As you can see, the useToggle hook here allows us to add additional behavior on toggle, but the real challenge is to also allow overriding of default props from getTogglerProps by passing in our own values to the function.

Now we look at the implementation of the toggle’s reducer, useToggle, and two helpers along with it.

function toggleReducer(state, {type}) {
  switch (type) {
    case 'toggle': {
      return {on: !state.on}
    }
    default: {
      throw new Error(`Unsupported type: ${type}`)
    }
  }
}

const callAll = (...fns) => (...args) => fns.forEach(fn => fn && fn(...args))
const noop = () => {}

function useToggle({onToggle = noop} = {}) {
  const [state, dispatch] = React.useReducer(toggleReducer, {on: false})
  const {on} = state

  function toggle() {
    const newOn = !on
    dispatch({type: 'toggle'})
    onToggle(newOn)
  }

  function getTogglerProps({onClick = noop, ...rest}) {
    return {
      'aria-pressed': on,
      onClick: callAll(onClick, toggle),
      ...rest,
    }


  return {
    on,
    toggle,
    getTogglerProps,
  }
}

We are making use of React.useReducer with our toggleReducer, which returns our current on state and the dispatcher that will allow us to toggle the state. The useToggle hook also contains two functions within its scope: toggle and getTogglerProps, which are both return in the object with the on state.

toggle simply contains all of the functionality we want to execute when the toggle is switched. We dispatch a toggle action to our reducer and we also execute the user’s passed onToggle function.

Also note in particular the callAll function. It allows us to execute several functions in the order that we pass them as arguments. So in this case, the new onClick returned in getTogglerProps will call both the onClick passed into getTogglerProps and our toggle function. Nice use of scope here if you ask me ?.

What we have in the end is a getTogglerProps that allows us to pass in our own props and our own onClick without overriding the default click behavior :).

Well, that’s it for now! Hope you enjoyed those examples. In the next few posts, I will go through some other patterns and also a review of the actual conference itself. Stay tuned! ?

*Not to hate on meetup.com. It’s great, and I’ve attended tons of great events thanks to it 🙂