Like many applications, the Slack desktop app logs how users interact with it. For example, it may log when a user views a screen or clicks on a button. Product Managers and Data Scientists analyze the logs, hoping to discover actionable insights to drive product refinements.

Slack’s desktop application is written in React. We built a React analytics logging library to: 1) increase developer velocity by making it easier to write logging code, 2) reduce log data errors, and 3) create a viewer to get a real-time look at logging. In this first (of two parts) article, we examine how we reached these goals, reviewing key pieces of the library: data sharing, logging impressions, and other events.

We’ve broken this post down into three sections:

  • Sharing Data Efficiently: how to share log data between components without manual data copying and code duplication — thereby increasing velocity and reducing data errors
  • Smarter Impressions: how we made it easy to log impressions while improving impression accuracy
  • Easy Event Logging: how we simplified logging events, like clicks, while bolstering data consistency

Let’s dive in!

Sharing Data Efficiently

Traditionally, analytics logging in React has been done by importing a logging function into a component file and calling it with specified data inside a lifecycle or event handler method. While this approach works, there is room for improvement.

To start, the approach leads to duplicate code. Say we want to log an impression when a user views a home page. We’ll import our logging function into our home page component file. Then, in our componentDidMount or useEffect-equivalent, we’ll call:

<span style="font-weight: 400">sendLog</span><span style="font-weight: 400">({ </span><span style="font-weight: 400">page:</span> <span style="font-weight: 400">'home'</span><span style="font-weight: 400">, </span><span style="font-weight: 400">action:</span> <span style="font-weight: 400">'impression'</span><span style="font-weight: 400"> });</span>

So far, so good. Next, say we want to log an impression of a section on the home page. We’ll find the section component file, import the <span style="font-weight: 400">sendLog</span> function, and in <span style="font-weight: 400">componentDidMount</span> call:

sendLog({ page: 'home', section: 'welcome section', action: 'impression' });

Alright, a bit of copying. Then, say we also want to log a banner that appears in the welcome section. Again, import <span style="font-weight: 400">sendLog</span> into the banner component, and in its <span style="font-weight: 400">componentDidMount</span> call:

sendLog({ page: 'home', section: 'welcome section', component: 'banner', elementName: 'promo banner', action: 'impression' }); 

I’m sure you see the issue. We have to import the <span style="font-weight: 400">sendLog</span> function into each file, repeatedly write code in <span style="font-weight: 400">componentDidMount</span>, and add the same key-values as we work our way down the component tree. We may even incorrectly copy a key-value, ruining data.

Can we improve this? As a React dev, your first instinct may be to use props to pass down <span style="font-weight: 400">sendLog</span> and our data object. That works. But what if the promo banner in our example is nested in a subsection of a subsection? Now you are “Prop-drilling” <span style="font-weight: 400">sendLog</span> and the data props through components that aren’t even logging.

React’s Context API allows us to share information between parent and child components no matter how far apart on the component tree without Prop-drilling. Let’s create a new Log component that uses the Context API to share our data props! The Log component also can contain our <span style="font-weight: 400">sendLog</span> function and logging logic, thereby keeping our substantive code separate from our logging code. That’s Separation of Concerns

To correctly share data props, we need the Log component to be a Provider of its data props to other child Log components somewhere down the component tree. Also, we need Log to be a Consumer of parent Log component data props. Here’s a skeleton:

<span style="font-weight: 400">import</span> <span style="font-weight: 400">React</span><span style="font-weight: 400">, { </span><span style="font-weight: 400">Component</span><span style="font-weight: 400"> } </span><span style="font-weight: 400">from</span> <span style="font-weight: 400">'react'</span><span style="font-weight: 400">;</span>
<span style="font-weight: 400">import</span><span style="font-weight: 400"> { </span><span style="font-weight: 400">sendLog</span><span style="font-weight: 400"> } </span><span style="font-weight: 400">from</span> <span style="font-weight: 400">'./utils'</span><span style="font-weight: 400">;</span>

<span style="font-weight: 400">const</span> <span style="font-weight: 400">Context</span><span style="font-weight: 400"> = </span><span style="font-weight: 400">React</span><span style="font-weight: 400">.</span><span style="font-weight: 400">createContext</span><span style="font-weight: 400">(</span><span style="font-weight: 400">''</span><span style="font-weight: 400">);</span>
<span style="font-weight: 400">export</span> <span style="font-weight: 400">const</span> <span style="font-weight: 400">LogContext</span><span style="font-weight: 400"> = </span><span style="font-weight: 400">Context</span><span style="font-weight: 400">;</span>

<span style="font-weight: 400">class</span> <span style="font-weight: 400">Log</span> <span style="font-weight: 400">extends</span> <span style="font-weight: 400">Component</span><span style="font-weight: 400"> {</span>
<span style="font-weight: 400">   </span><span style="font-weight: 400">componentDidMount</span><span style="font-weight: 400">() {</span>
<span style="font-weight: 400">       </span><span style="font-weight: 400">// some code that calls sendLog</span>
<span style="font-weight: 400">   }</span>

<span style="font-weight: 400">   </span><span style="font-weight: 400">render</span><span style="font-weight: 400">() {</span>
<span style="font-weight: 400">       </span><span style="font-weight: 400">const</span><span style="font-weight: 400"> { </span><span style="font-weight: 400">children</span><span style="font-weight: 400">, ...</span><span style="font-weight: 400">directProps</span><span style="font-weight: 400"> } = </span><span style="font-weight: 400">this</span><span style="font-weight: 400">.</span><span style="font-weight: 400">props</span><span style="font-weight: 400">;</span>

<span style="font-weight: 400">       </span><span style="font-weight: 400">return</span><span style="font-weight: 400"> (</span>
<span style="font-weight: 400">           </span><span style="font-weight: 400"><</span><span style="font-weight: 400">LogContext.Consumer</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">               </span><span style="font-weight: 400">{</span><span style="font-weight: 400">(</span><span style="font-weight: 400">consumedProps</span><span style="font-weight: 400">) </span><span style="font-weight: 400">=></span><span style="font-weight: 400"> {</span>
<span style="font-weight: 400">                   </span><span style="font-weight: 400">const</span> <span style="font-weight: 400">combinedProps</span><span style="font-weight: 400"> = { ...</span><span style="font-weight: 400">consumedProps</span><span style="font-weight: 400">, ...</span><span style="font-weight: 400">directProps</span><span style="font-weight: 400"> };</span>
<span style="font-weight: 400">                   </span><span style="font-weight: 400">return</span> <span style="font-weight: 400"><</span><span style="font-weight: 400">LogContext.Provider</span> <span style="font-weight: 400">value</span><span style="font-weight: 400">=</span><span style="font-weight: 400">{</span><span style="font-weight: 400">combinedProps</span><span style="font-weight: 400">}</span><span style="font-weight: 400">></span><span style="font-weight: 400">{</span><span style="font-weight: 400">children</span><span style="font-weight: 400">}</span><span style="font-weight: 400"></</span><span style="font-weight: 400">LogContext.Provider</span><span style="font-weight: 400">></span><span style="font-weight: 400">;</span>
<span style="font-weight: 400">               }</span><span style="font-weight: 400">}</span>
<span style="font-weight: 400">           </span><span style="font-weight: 400"></</span><span style="font-weight: 400">LogContext.Consumer</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">       );</span>
<span style="font-weight: 400">   }</span>
<span style="font-weight: 400">}</span>

<span style="font-weight: 400">export</span> <span style="font-weight: 400">default</span> <span style="font-weight: 400">Log</span><span style="font-weight: 400">;</span>

We use <span style="font-weight: 400">LogContext.Consumer</span> to consume data props from all parent Log components and then, after combining them with directly-passed data props, we pass the <span style="font-weight: 400">combinedProps</span> down to other Log components through <span style="font-weight: 400">LogContext.Provider</span>. No more Prop-drilling or manual data copying!

Smarter Impressions

Now that we can share data effectively, let’s add impression logging functionality. We want data sharing and impression logging to work with this example usage:

<span style="font-weight: 400"><</span><span style="font-weight: 400">Log</span> <span style="font-weight: 400">page</span><span style="font-weight: 400">=</span><span style="font-weight: 400">"home"</span><span style="font-weight: 400">></span>
    <span style="font-weight: 400"><</span><span style="font-weight: 400">div</span> <span style="font-weight: 400">id</span><span style="font-weight: 400">=</span><span style="font-weight: 400">"home"</span><span style="font-weight: 400">></span>
        <span style="font-weight: 400"><</span><span style="font-weight: 400">Log</span> <span style="font-weight: 400">logImpression</span> <span style="font-weight: 400">section</span><span style="font-weight: 400">=</span><span style="font-weight: 400">"welcome"</span><span style="font-weight: 400">></span>
            <span style="font-weight: 400"><</span><span style="font-weight: 400">div</span> <span style="font-weight: 400">id</span><span style="font-weight: 400">=</span><span style="font-weight: 400">"welcome-section"</span><span style="font-weight: 400">></span><span style="font-weight: 400">Welcome</span><span style="font-weight: 400"></</span><span style="font-weight: 400">div</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">        </</span><span style="font-weight: 400">Log</span><span style="font-weight: 400">></span>
    <span style="font-weight: 400"></</span><span style="font-weight: 400">div</span><span style="font-weight: 400">></span>
<span style="font-weight: 400"></</span><span style="font-weight: 400">Log</span><span style="font-weight: 400">></span>

In the example code, we want to set a data tag on the home page, <span style="font-weight: 400">page="home"</span>. But we aren’t logging a page impression. We want to log an impression of the welcome section. To do so, we pass a <span style="font-weight: 400">logImpression</span> prop into the Log component wrapping the welcome section (along with another data prop, <span style="font-weight: 400">section="welcome"</span>).

To make the Log component send an impression of the welcome section, we could check in Log’s <span style="font-weight: 400">componentDidMount</span> whether <span style="font-weight: 400">logImpression</span> exists and, if it does, call <span style="font-weight: 400">sendLog</span>. That way, when Log and its wrapped child (the welcome section) mount, an impression is logged. Not bad. But what if the welcome section is mounted off the screen? Say at the bottom of a tall page where the user has to scroll down to actually see it: 

<span style="font-weight: 400"><</span><span style="font-weight: 400">Log</span> <span style="font-weight: 400">page</span><span style="font-weight: 400">=</span><span style="font-weight: 400">"home"</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">    <</span><span style="font-weight: 400">div</span> <span style="font-weight: 400">id</span><span style="font-weight: 400">=</span><span style="font-weight: 400">"home"</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">        <</span><span style="font-weight: 400">div</span> <span style="font-weight: 400">style</span><span style="font-weight: 400">=</span><span style="font-weight: 400">{</span><span style="font-weight: 400">{ </span><span style="font-weight: 400">height:</span> <span style="font-weight: 400">"2000px"</span><span style="font-weight: 400"> }</span><span style="font-weight: 400">}</span><span style="font-weight: 400">></span><span style="font-weight: 400">I'm really tall!</span><span style="font-weight: 400"></</span><span style="font-weight: 400">div</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">        <</span><span style="font-weight: 400">Log</span> <span style="font-weight: 400">logImpression</span> <span style="font-weight: 400">section</span><span style="font-weight: 400">=</span><span style="font-weight: 400">"welcome"</span><span style="font-weight: 400">></span>
            <span style="font-weight: 400"><</span><span style="font-weight: 400">div</span> <span style="font-weight: 400">id</span><span style="font-weight: 400">=</span><span style="font-weight: 400">"welcome-section"</span><span style="font-weight: 400">></span><span style="font-weight: 400">Welcome!</span><span style="font-weight: 400"></</span><span style="font-weight: 400">div</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">        </span><span style="font-weight: 400"></</span><span style="font-weight: 400">Log</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">    </</span><span style="font-weight: 400">div</span><span style="font-weight: 400">></span>
<span style="font-weight: 400"></</span><span style="font-weight: 400">Log</span><span style="font-weight: 400">></span>

Obviously, we don’t want to log the impression until the user actually scrolls the welcome section into view. To create smarter impression logging, let’s use the Intersection Observer API

First, in Log’s <span style="font-weight: 400">componentDidMount</span>, we check if the <span style="font-weight: 400">logImpression</span> prop is true. If it is true, we call the <span style="font-weight: 400">setupObserver</span> method to instantiate an Intersection Observer instance: 

<span style="font-weight: 400">   </span><span style="font-weight: 400">componentDidMount</span><span style="font-weight: 400">() {</span>
<span style="font-weight: 400">       </span><span style="font-weight: 400">if</span><span style="font-weight: 400"> (</span><span style="font-weight: 400">this</span><span style="font-weight: 400">.</span><span style="font-weight: 400">props</span><span style="font-weight: 400">.</span><span style="font-weight: 400">logImpression</span><span style="font-weight: 400">) {</span>
<span style="font-weight: 400">           </span><span style="font-weight: 400">this</span><span style="font-weight: 400">.</span><span style="font-weight: 400">setupObserver</span><span style="font-weight: 400">();</span>
<span style="font-weight: 400">       }</span>
<span style="font-weight: 400">   }</span>

<span style="font-weight: 400">   </span><span style="font-weight: 400">setupObserver</span><span style="font-weight: 400">() {</span>
<span style="font-weight: 400">       </span><span style="font-weight: 400">this</span><span style="font-weight: 400">.</span><span style="font-weight: 400">observer</span><span style="font-weight: 400"> = </span><span style="font-weight: 400">new</span> <span style="font-weight: 400">IntersectionObserver</span><span style="font-weight: 400">(</span><span style="font-weight: 400">this</span><span style="font-weight: 400">.</span><span style="font-weight: 400">observerCallback</span><span style="font-weight: 400">, {</span>
<span style="font-weight: 400">           </span><span style="font-weight: 400">root:</span> <span style="font-weight: 400">null</span><span style="font-weight: 400">,</span>
<span style="font-weight: 400">           </span><span style="font-weight: 400">rootMargin:</span> <span style="font-weight: 400">'0px'</span><span style="font-weight: 400">,</span>
<span style="font-weight: 400">           </span><span style="font-weight: 400">threshold:</span> <span style="font-weight: 400">0</span><span style="font-weight: 400">,</span>
<span style="font-weight: 400">       });</span>

<span style="font-weight: 400">       </span><span style="font-weight: 400">// add code to observe our wrapped child element with this.observer</span>
<span style="font-weight: 400">   }</span>

When creating the instance, we set the <span style="font-weight: 400">root</span> to <span style="font-weight: 400">null</span>, meaning we are checking when our observed element intersects the viewport, i.e., enters the screen. We also set the <span style="font-weight: 400">rootMargin</span> and <span style="font-weight: 400">threshold</span> params to 0 to avoid any offsets.

Next, in Log’s <span style="font-weight: 400">render</span> method, we wrap the Log component’s children in a div element. We need the additional div because we need to use React Ref to find our observed child element on the DOM. The div provides the ref:

<span style="font-weight: 400">  </span><span style="font-weight: 400">constructor</span><span style="font-weight: 400">(</span><span style="font-weight: 400">props</span><span style="font-weight: 400">) {</span>
<span style="font-weight: 400">       </span><span style="font-weight: 400">super</span><span style="font-weight: 400">(</span><span style="font-weight: 400">props</span><span style="font-weight: 400">);</span>

<span style="font-weight: 400">       </span><span style="font-weight: 400">this</span><span style="font-weight: 400">.</span><span style="font-weight: 400">logDOMElementRef</span><span style="font-weight: 400"> = </span><span style="font-weight: 400">React</span><span style="font-weight: 400">.</span><span style="font-weight: 400">createRef</span><span style="font-weight: 400">();</span>
<span style="font-weight: 400">   }   </span>

<span style="font-weight: 400">   render</span><span style="font-weight: 400">() {</span>
<span style="font-weight: 400">       </span><span style="font-weight: 400">const</span><span style="font-weight: 400"> { </span><span style="font-weight: 400">children</span><span style="font-weight: 400">, </span><span style="font-weight: 400">logImpression</span><span style="font-weight: 400">, ...</span><span style="font-weight: 400">directProps</span><span style="font-weight: 400"> } = </span><span style="font-weight: 400">this</span><span style="font-weight: 400">.</span><span style="font-weight: 400">props</span><span style="font-weight: 400">;</span>

<span style="font-weight: 400">       </span><span style="font-weight: 400">return</span><span style="font-weight: 400"> (</span>
<span style="font-weight: 400">           </span><span style="font-weight: 400"><</span><span style="font-weight: 400">LogContext.Consumer</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">               </span><span style="font-weight: 400">{</span><span style="font-weight: 400">(</span><span style="font-weight: 400">consumedProps</span><span style="font-weight: 400">) </span><span style="font-weight: 400">=></span><span style="font-weight: 400"> {</span>
<span style="font-weight: 400">                   </span><span style="font-weight: 400">this</span><span style="font-weight: 400">.</span><span style="font-weight: 400">combinedProps</span><span style="font-weight: 400"> = { ...</span><span style="font-weight: 400">consumedProps</span><span style="font-weight: 400">, ...</span><span style="font-weight: 400">directProps</span><span style="font-weight: 400"> };</span>

<span style="font-weight: 400">                   </span><span style="font-weight: 400">return</span><span style="font-weight: 400"> (</span>
<span style="font-weight: 400">                       </span><span style="font-weight: 400"><</span><span style="font-weight: 400">Context.Provider</span> <span style="font-weight: 400">value</span><span style="font-weight: 400">=</span><span style="font-weight: 400">{this</span><span style="font-weight: 400">.</span><span style="font-weight: 400">combinedProps</span><span style="font-weight: 400">}</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">                           </span><span style="font-weight: 400"><</span><span style="font-weight: 400">div</span> <span style="font-weight: 400">style</span><span style="font-weight: 400">=</span><span style="font-weight: 400">{</span><span style="font-weight: 400">{ </span><span style="font-weight: 400">display:</span> <span style="font-weight: 400">'contents'</span><span style="font-weight: 400"> }</span><span style="font-weight: 400">}</span> <span style="font-weight: 400">ref</span><span style="font-weight: 400">=</span><span style="font-weight: 400">{this</span><span style="font-weight: 400">.</span><span style="font-weight: 400">logDOMElementRef</span><span style="font-weight: 400">}</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">                               </span><span style="font-weight: 400">{</span><span style="font-weight: 400">children</span><span style="font-weight: 400">}</span>
<span style="font-weight: 400">                           </span><span style="font-weight: 400"></</span><span style="font-weight: 400">div</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">                       </span><span style="font-weight: 400"></</span><span style="font-weight: 400">Context.Provider</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">                   );</span>
<span style="font-weight: 400">               }</span><span style="font-weight: 400">}</span>
<span style="font-weight: 400">           </span><span style="font-weight: 400"></</span><span style="font-weight: 400">LogContext.Consumer</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">       );</span>
<span style="font-weight: 400">   }</span>

We have to be careful here because we don’t want the additional div to impact UI layout. To prevent that, we set the div’s CSS display property to <span style="font-weight: 400">contents</span>

Now that we have our ref, we find the Log component’s first visible child element by finding the first child whose <span style="font-weight: 400">offsetParent</span> is not <span style="font-weight: 400">null</span>. This is important because an element’s <span style="font-weight: 400">offsetParent</span> is <span style="font-weight: 400">null</span> if its <span style="font-weight: 400">display</span> property is <span style="font-weight: 400">none</span>. Once we have found our visible child element, we tell our observer to observe it! Let’s update <span style="font-weight: 400">setupObserver</span> accordingly:

<span style="font-weight: 400">   </span><span style="font-weight: 400">setupObserver</span><span style="font-weight: 400">() {</span>
<span style="font-weight: 400">       </span><span style="font-weight: 400">this</span><span style="font-weight: 400">.</span><span style="font-weight: 400">observer</span><span style="font-weight: 400"> = </span><span style="font-weight: 400">new</span> <span style="font-weight: 400">IntersectionObserver</span><span style="font-weight: 400">(</span><span style="font-weight: 400">this</span><span style="font-weight: 400">.</span><span style="font-weight: 400">observerCallback</span><span style="font-weight: 400">, {</span>
<span style="font-weight: 400">           </span><span style="font-weight: 400">root:</span> <span style="font-weight: 400">null</span><span style="font-weight: 400">,</span>
<span style="font-weight: 400">           </span><span style="font-weight: 400">rootMargin:</span> <span style="font-weight: 400">'0px'</span><span style="font-weight: 400">,</span>
<span style="font-weight: 400">           </span><span style="font-weight: 400">threshold:</span> <span style="font-weight: 400">0</span><span style="font-weight: 400">,</span>
<span style="font-weight: 400">       });</span>

<span style="font-weight: 400">       </span><span style="font-weight: 400">const</span> <span style="font-weight: 400">wrappedDOMElements</span><span style="font-weight: 400"> = </span><span style="font-weight: 400">this</span><span style="font-weight: 400">.</span><span style="font-weight: 400">logDOMElementRef</span><span style="font-weight: 400">?.</span><span style="font-weight: 400">current</span><span style="font-weight: 400">?.</span><span style="font-weight: 400">childNodes</span><span style="font-weight: 400">;</span>
<span style="font-weight: 400">       </span><span style="font-weight: 400">const</span> <span style="font-weight: 400">firstVisibleElement</span><span style="font-weight: 400"> = </span><span style="font-weight: 400">find</span><span style="font-weight: 400">(</span><span style="font-weight: 400">wrappedDOMElements</span><span style="font-weight: 400">, (</span><span style="font-weight: 400">el</span><span style="font-weight: 400">) </span><span style="font-weight: 400">=></span> <span style="font-weight: 400">el</span><span style="font-weight: 400">.</span><span style="font-weight: 400">offsetParent</span><span style="font-weight: 400"> !== </span><span style="font-weight: 400">null</span><span style="font-weight: 400">);</span>

<span style="font-weight: 400">       </span><span style="font-weight: 400">if</span><span style="font-weight: 400"> (</span><span style="font-weight: 400">firstVisibleElement</span><span style="font-weight: 400">) {</span>
<span style="font-weight: 400">           </span><span style="font-weight: 400">this</span><span style="font-weight: 400">.</span><span style="font-weight: 400">observer</span><span style="font-weight: 400">.</span><span style="font-weight: 400">observe</span><span style="font-weight: 400">(</span><span style="font-weight: 400">firstVisibleElement</span><span style="font-weight: 400">);</span>
<span style="font-weight: 400">       }</span>
<span style="font-weight: 400">   }</span>

An observer’s first argument is a callback. Ours is conspicuously named <span style="font-weight: 400">observerCallback</span>. When an observed child element comes into view, <span style="font-weight: 400">observerCallback</span> is called:

<span style="font-weight: 400">   </span><span style="font-weight: 400">constructor</span><span style="font-weight: 400">(</span><span style="font-weight: 400">props</span><span style="font-weight: 400">) {</span>
<span style="font-weight: 400">       </span><span style="font-weight: 400">super</span><span style="font-weight: 400">(</span><span style="font-weight: 400">props</span><span style="font-weight: 400">);</span>

<span style="font-weight: 400">       </span><span style="font-weight: 400">this</span><span style="font-weight: 400">.</span><span style="font-weight: 400">logDOMElementRef</span><span style="font-weight: 400"> = </span><span style="font-weight: 400">React</span><span style="font-weight: 400">.</span><span style="font-weight: 400">createRef</span><span style="font-weight: 400">();</span>
<span style="font-weight: 400">       </span><span style="font-weight: 400">this</span><span style="font-weight: 400">.</span><span style="font-weight: 400">state</span><span style="font-weight: 400"> = { </span><span style="font-weight: 400">isInViewport:</span> <span style="font-weight: 400">false</span><span style="font-weight: 400"> };</span>
<span style="font-weight: 400">       this</span><span style="font-weight: 400">.</span><span style="font-weight: 400">hasImpressionAlreadyBeenLogged</span><span style="font-weight: 400"> = </span><span style="font-weight: 400">false</span><span style="font-weight: 400">;</span>
<span style="font-weight: 400">       </span><span style="font-weight: 400">this</span><span style="font-weight: 400">.</span><span style="font-weight: 400">observerCallback</span><span style="font-weight: 400"> = </span><span style="font-weight: 400">this</span><span style="font-weight: 400">.</span><span style="font-weight: 400">observerCallback</span><span style="font-weight: 400">.</span><span style="font-weight: 400">bind</span><span style="font-weight: 400">(</span><span style="font-weight: 400">this</span><span style="font-weight: 400">);</span>
<span style="font-weight: 400">   }</span>

<span style="font-weight: 400">   observerCallback</span><span style="font-weight: 400">(</span><span style="font-weight: 400">entries</span><span style="font-weight: 400">) {</span>
<span style="font-weight: 400">       </span><span style="font-weight: 400">const</span> <span style="font-weight: 400">entry</span><span style="font-weight: 400"> = </span><span style="font-weight: 400">entries</span><span style="font-weight: 400">[</span><span style="font-weight: 400">0</span><span style="font-weight: 400">];</span>

<span style="font-weight: 400">       </span><span style="font-weight: 400">if</span><span style="font-weight: 400"> (</span><span style="font-weight: 400">entry</span><span style="font-weight: 400"> !== </span><span style="font-weight: 400">undefined</span><span style="font-weight: 400"> && </span><span style="font-weight: 400">this</span><span style="font-weight: 400">.</span><span style="font-weight: 400">state</span><span style="font-weight: 400">.</span><span style="font-weight: 400">isInViewport</span><span style="font-weight: 400"> !== </span><span style="font-weight: 400">entry</span><span style="font-weight: 400">.</span><span style="font-weight: 400">isIntersecting</span><span style="font-weight: 400">) {</span>
<span style="font-weight: 400">           </span><span style="font-weight: 400">this</span><span style="font-weight: 400">.</span><span style="font-weight: 400">setState</span><span style="font-weight: 400">(() </span><span style="font-weight: 400">=></span><span style="font-weight: 400"> ({</span>
<span style="font-weight: 400">               </span><span style="font-weight: 400">isInViewport:</span> <span style="font-weight: 400">entry</span><span style="font-weight: 400">.</span><span style="font-weight: 400">isIntersecting</span><span style="font-weight: 400">,</span>
<span style="font-weight: 400">           }));</span>
<span style="font-weight: 400">       }</span>
<span style="font-weight: 400">   }</span>

In <span style="font-weight: 400">observerCallback</span>, we check if our entry, i.e. observed child, is intersecting the viewport and update our Log component’s state <span style="font-weight: 400">isInViewport</span> value accordingly. The state update triggers componentDidUpdate, which checks if the <span style="font-weight: 400">isInViewport</span> value is now true:

<span style="font-weight: 400">   </span><span style="font-weight: 400">componentDidUpdate</span><span style="font-weight: 400">() {</span>
<span style="font-weight: 400">       </span><span style="font-weight: 400">if</span><span style="font-weight: 400"> (</span>
<span style="font-weight: 400">           </span><span style="font-weight: 400">this</span><span style="font-weight: 400">.</span><span style="font-weight: 400">props</span><span style="font-weight: 400">.</span><span style="font-weight: 400">logImpression</span><span style="font-weight: 400"> &&</span>
<span style="font-weight: 400">           </span><span style="font-weight: 400">this</span><span style="font-weight: 400">.</span><span style="font-weight: 400">state</span><span style="font-weight: 400">.</span><span style="font-weight: 400">isInViewport</span><span style="font-weight: 400"> &&</span>
<span style="font-weight: 400">           !</span><span style="font-weight: 400">this</span><span style="font-weight: 400">.</span><span style="font-weight: 400">hasImpressionAlreadyBeenLogged</span>
<span style="font-weight: 400">       ) {</span>
<span style="font-weight: 400">           </span><span style="font-weight: 400">sendLog</span><span style="font-weight: 400">(</span><span style="font-weight: 400">this</span><span style="font-weight: 400">.</span><span style="font-weight: 400">combinedProps</span><span style="font-weight: 400">);</span>
<span style="font-weight: 400">           </span><span style="font-weight: 400">this</span><span style="font-weight: 400">.</span><span style="font-weight: 400">hasImpressionAlreadyBeenLogged</span><span style="font-weight: 400"> = </span><span style="font-weight: 400">true</span><span style="font-weight: 400">;</span>
<span style="font-weight: 400">       }</span>
<span style="font-weight: 400">   }</span>

If <span style="font-weight: 400">isInViewport</span> is true, and an impression has not previously been logged (this.hasImpressionAlreadyBeenLogged is still false), we call <span style="font-weight: 400">sendLog</span> with <span style="font-weight: 400">this.combinedProps</span>

Since we are using the Context API, our Log component has inherited the <span style="font-weight: 400">page="home"</span> data tag prop from its parent Log component. Thus, <span style="font-weight: 400">this.combinedProps</span> already contains the page tag along with the directly-passed <span style="font-weight: 400">section="welcome"</span> tag. We didn’t have to copy anything, and an impression is sent when the component is scrolled into view with all the data we want!

Clean Event Logging

We’ve improved data sharing and impression logging. Let’s see if we can improve logging events like button clicks, typing in an input, and choosing a dropdown option. 

Traditionally, event logging has been done by adding logging code into event handler methods alongside the substantive code. Remember our promo banner? Say it has a close button on it. Following the traditional way, we’ll have an <span style="font-weight: 400">onClick</span> property on the button component, with a <span style="font-weight: 400">handleClick</span> event handler as its value. In <span style="font-weight: 400">handleClick</span>, we’ll have code that hides the banner and calls <span style="font-weight: 400">sendLog</span>. We would want the click data to contain the context of where the button is on the page. So we would call: 

<span style="font-weight: 400">sendLog</span><span style="font-weight: 400">({ </span><span style="font-weight: 400">page:</span> <span style="font-weight: 400">'home'</span><span style="font-weight: 400">, </span><span style="font-weight: 400">section:</span> <span style="font-weight: 400">'welcome section'</span><span style="font-weight: 400">, </span><span style="font-weight: 400">component:</span> <span style="font-weight: 400">'banner'</span><span style="font-weight: 400">, </span><span style="font-weight: 400">action:</span> <span style="font-weight: 400">'click'</span><span style="font-weight: 400">, </span><span style="font-weight: 400">elementType:</span> <span style="font-weight: 400">'button'</span><span style="font-weight: 400">, </span><span style="font-weight: 400">elementName:</span> <span style="font-weight: 400">'promo-close-button'</span><span style="font-weight: 400"> });</span>

Examining the <span style="font-weight: 400">sendLog</span> data, we may ask: what are the data tags that are actually unique to this close button? Just one: <span style="font-weight: 400">elementName: 'promo-close-button'</span>. The data tags <span style="font-weight: 400">page: 'home'</span>, <span style="font-weight: 400">section: 'welcome section'</span>, and <span style="font-weight: 400">component: 'banner'</span> all provide context but aren’t used only for the button. Meanwhile, <span style="font-weight: 400">action: 'click'</span> and <span style="font-weight: 400">elementType: 'button'</span> are likely the same for all buttons everywhere in our application. So ideally we would just want to pass <span style="font-weight: 400">elementName: 'promo-close-button'</span> directly into our close button:

<span style="font-weight: 400"><</span><span style="font-weight: 400">Log</span> <span style="font-weight: 400">page</span><span style="font-weight: 400">=</span><span style="font-weight: 400">"home"</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">    <</span><span style="font-weight: 400">div</span> <span style="font-weight: 400">id</span><span style="font-weight: 400">=</span><span style="font-weight: 400">"home"</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">        Home! </span><span style="font-weight: 400">{</span><span style="font-weight: 400">/* more home screen jsx */</span><span style="font-weight: 400">}</span>
<span style="font-weight: 400">        </span><span style="font-weight: 400"><</span><span style="font-weight: 400">Log</span> <span style="font-weight: 400">logImpression</span> <span style="font-weight: 400">section</span><span style="font-weight: 400">=</span><span style="font-weight: 400">"welcome"</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">            </span><span style="font-weight: 400"><</span><span style="font-weight: 400">div</span> <span style="font-weight: 400">id</span><span style="font-weight: 400">=</span><span style="font-weight: 400">"welcome-section"</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">                Welcome! </span><span style="font-weight: 400">{</span><span style="font-weight: 400">/* more welcome section jsx */</span><span style="font-weight: 400">}</span>
<span style="font-weight: 400">                </span><span style="font-weight: 400"><</span><span style="font-weight: 400">Log</span> <span style="font-weight: 400">logImpression</span> <span style="font-weight: 400">component</span><span style="font-weight: 400">=</span><span style="font-weight: 400">"banner"</span> <span style="font-weight: 400">elementName</span><span style="font-weight: 400">=</span><span style="font-weight: 400">"promo-banner"</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">                    </span><span style="font-weight: 400"><</span><span style="font-weight: 400">div</span> <span style="font-weight: 400">id</span><span style="font-weight: 400">=</span><span style="font-weight: 400">"promo-banner"</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">                        Promo! </span><span style="font-weight: 400">{</span><span style="font-weight: 400">/* more banner jsx */</span><span style="font-weight: 400">}</span>
<span style="font-weight: 400">                        </span><span style="font-weight: 400"><</span><span style="font-weight: 400">Button</span>
<span style="font-weight: 400">                            </span><span style="font-weight: 400">text</span><span style="font-weight: 400">=</span><span style="font-weight: 400">"Close!"</span>
<span style="font-weight: 400">                            </span><span style="font-weight: 400">onClick</span><span style="font-weight: 400">=</span><span style="font-weight: 400">{this</span><span style="font-weight: 400">.</span><span style="font-weight: 400">handleClick</span><span style="font-weight: 400">}</span>
<span style="font-weight: 400">                            </span><span style="font-weight: 400">logEventProps</span><span style="font-weight: 400">=</span><span style="font-weight: 400">{</span><span style="font-weight: 400">{</span>
<span style="font-weight: 400">                                </span><span style="font-weight: 400">elementName:</span> <span style="font-weight: 400">'promo-close-button'</span><span style="font-weight: 400">,</span>
<span style="font-weight: 400">                            }</span><span style="font-weight: 400">}</span>
<span style="font-weight: 400">                        </span><span style="font-weight: 400">/></span>
<span style="font-weight: 400">                    </span><span style="font-weight: 400"></</span><span style="font-weight: 400">div</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">                </span><span style="font-weight: 400"></</span><span style="font-weight: 400">Log</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">            </span><span style="font-weight: 400"></</span><span style="font-weight: 400">div</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">        </span><span style="font-weight: 400"></</span><span style="font-weight: 400">Log</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">    </span><span style="font-weight: 400"></</span><span style="font-weight: 400">div</span><span style="font-weight: 400">></span>
<span style="font-weight: 400"></</span><span style="font-weight: 400">Log</span><span style="font-weight: 400">></span>

*The ugly nesting is just so you can see everything together. In a real implementation, each div would likely be its own component with a Log component wrapping it.

In the code, we pass <span style="font-weight: 400">logEventProps</span> directly into the Button component, with the one data tag we need, <span style="font-weight: 400">elementName: 'promo-close-button'</span>. In order to make this work, we must prepare our Button component to use <span style="font-weight: 400">logEventProps</span>. We wrap Button with a new component, LogEvent:

<span style="font-weight: 400">export</span> <span style="font-weight: 400">default</span> <span style="font-weight: 400">function</span> <span style="font-weight: 400">Button</span><span style="font-weight: 400">({ </span><span style="font-weight: 400">logEventProps</span><span style="font-weight: 400">, </span><span style="font-weight: 400">onClick</span><span style="font-weight: 400">, </span><span style="font-weight: 400">text</span><span style="font-weight: 400"> }) {</span>
<span style="font-weight: 400">   </span><span style="font-weight: 400">return</span><span style="font-weight: 400"> (</span>
<span style="font-weight: 400">       </span><span style="font-weight: 400"><</span><span style="font-weight: 400">LogEvent</span>
<span style="font-weight: 400">           </span><span style="font-weight: 400">logEventProps</span><span style="font-weight: 400">=</span><span style="font-weight: 400">{</span><span style="font-weight: 400">logEventProps</span><span style="font-weight: 400">}</span>
<span style="font-weight: 400">           </span><span style="font-weight: 400">actionProps</span><span style="font-weight: 400">=</span><span style="font-weight: 400">{</span><span style="font-weight: 400">{ </span><span style="font-weight: 400">onClick:</span><span style="font-weight: 400"> { </span><span style="font-weight: 400">action:</span> <span style="font-weight: 400">'click'</span><span style="font-weight: 400"> } }</span><span style="font-weight: 400">}</span>
<span style="font-weight: 400">           </span><span style="font-weight: 400">elementType</span><span style="font-weight: 400">=</span><span style="font-weight: 400">"button"</span>
<span style="font-weight: 400">       </span><span style="font-weight: 400">></span>
<span style="font-weight: 400">           </span><span style="font-weight: 400"><</span><span style="font-weight: 400">button</span> <span style="font-weight: 400">onClick</span><span style="font-weight: 400">=</span><span style="font-weight: 400">{</span><span style="font-weight: 400">onClick</span><span style="font-weight: 400">}</span><span style="font-weight: 400">></span><span style="font-weight: 400">{</span><span style="font-weight: 400">text</span><span style="font-weight: 400">}</span><span style="font-weight: 400"></</span><span style="font-weight: 400">button</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">       </span><span style="font-weight: 400"></</span><span style="font-weight: 400">LogEvent</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">   );</span>
<span style="font-weight: 400">}</span>

In our new LogEvent component, we specify that the Button component’s data tag <span style="font-weight: 400">elementType</span> is always <span style="font-weight: 400">'button'</span>. Further, on an <span style="font-weight: 400">onClick</span> event, the Button’s <span style="font-weight: 400">action</span> tag is always <span style="font-weight: 400">'click'</span>. By placing these tags in the LogEvent component that wraps Button, we never have to copy them again when we log a click of a Button instance anywhere in our application!  

Well how does a basic LogEvent component work?

<span style="font-weight: 400">import React from 'react';</span>
<span style="font-weight: 400">import</span><span style="font-weight: 400"> { </span><span style="font-weight: 400">forEach</span><span style="font-weight: 400"> } </span><span style="font-weight: 400">from</span> <span style="font-weight: 400">'lodash'</span><span style="font-weight: 400">;</span>
<span style="font-weight: 400">import</span> <span style="font-weight: 400">Log</span><span style="font-weight: 400">, { </span><span style="font-weight: 400">LogContext</span><span style="font-weight: 400"> } </span><span style="font-weight: 400">from</span> <span style="font-weight: 400">'./log'</span><span style="font-weight: 400">;</span>
<span style="font-weight: 400">import</span><span style="font-weight: 400"> { </span><span style="font-weight: 400">sendLog</span><span style="font-weight: 400"> } </span><span style="font-weight: 400">from</span> <span style="font-weight: 400">'./utils'</span><span style="font-weight: 400">;</span>

<span style="font-weight: 400">function</span> <span style="font-weight: 400">getEventHandlers</span><span style="font-weight: 400">(</span><span style="font-weight: 400">child</span><span style="font-weight: 400">, </span><span style="font-weight: 400">consumedProps</span><span style="font-weight: 400">, </span><span style="font-weight: 400">actionProps</span><span style="font-weight: 400">) {</span>
<span style="font-weight: 400">   </span><span style="font-weight: 400">// create modified event handlers that log</span>
<span style="font-weight: 400">}</span>

<span style="font-weight: 400">export</span> <span style="font-weight: 400">default</span> <span style="font-weight: 400">function</span> <span style="font-weight: 400">LogEvent</span><span style="font-weight: 400">(</span><span style="font-weight: 400">props</span><span style="font-weight: 400">) {</span>
<span style="font-weight: 400">   </span><span style="font-weight: 400">const</span><span style="font-weight: 400"> { </span><span style="font-weight: 400">elementType</span><span style="font-weight: 400">, </span><span style="font-weight: 400">actionProps</span><span style="font-weight: 400">, </span><span style="font-weight: 400">logEventProps</span><span style="font-weight: 400">, </span><span style="font-weight: 400">children</span><span style="font-weight: 400"> } = </span><span style="font-weight: 400">props</span><span style="font-weight: 400">;
</span>
<span style="font-weight: 400">   </span><span style="font-weight: 400">if</span><span style="font-weight: 400"> (!</span><span style="font-weight: 400">logEventProps</span><span style="font-weight: 400"> || !</span><span style="font-weight: 400">actionProps</span><span style="font-weight: 400">) {</span>
<span style="font-weight: 400">       </span><span style="font-weight: 400">return</span> <span style="font-weight: 400">children</span><span style="font-weight: 400">;</span>
<span style="font-weight: 400">   }</span>

<span style="font-weight: 400">   </span><span style="font-weight: 400">return</span><span style="font-weight: 400"> (</span>
<span style="font-weight: 400">       </span><span style="font-weight: 400"><</span><span style="font-weight: 400">Log</span> <span style="font-weight: 400">elementType</span><span style="font-weight: 400">=</span><span style="font-weight: 400">{</span><span style="font-weight: 400">elementType</span><span style="font-weight: 400">}</span> <span style="font-weight: 400">{</span><span style="font-weight: 400">...</span><span style="font-weight: 400">logEventProps</span><span style="font-weight: 400">}</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">           </span><span style="font-weight: 400"><</span><span style="font-weight: 400">LogContext.Consumer</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">               </span><span style="font-weight: 400">{</span><span style="font-weight: 400">(</span><span style="font-weight: 400">consumedProps</span><span style="font-weight: 400">) </span><span style="font-weight: 400">=></span>
<span style="font-weight: 400">                   </span><span style="font-weight: 400">React</span><span style="font-weight: 400">.</span><span style="font-weight: 400">Children</span><span style="font-weight: 400">.</span><span style="font-weight: 400">map</span><span style="font-weight: 400">(</span><span style="font-weight: 400">children</span><span style="font-weight: 400">, (</span><span style="font-weight: 400">child</span><span style="font-weight: 400">) </span><span style="font-weight: 400">=></span>
<span style="font-weight: 400">                       </span><span style="font-weight: 400">React</span><span style="font-weight: 400">.</span><span style="font-weight: 400">cloneElement</span><span style="font-weight: 400">(</span>
<span style="font-weight: 400">                           </span><span style="font-weight: 400">child</span><span style="font-weight: 400">,</span>
<span style="font-weight: 400">                           </span><span style="font-weight: 400">getEventHandlers</span><span style="font-weight: 400">(</span><span style="font-weight: 400">child</span><span style="font-weight: 400">, </span><span style="font-weight: 400">consumedProps</span><span style="font-weight: 400">, </span><span style="font-weight: 400">actionProps</span><span style="font-weight: 400">)</span>
<span style="font-weight: 400">                       )</span>
<span style="font-weight: 400">                   )</span>
<span style="font-weight: 400">               </span><span style="font-weight: 400">}</span>
<span style="font-weight: 400">           </span><span style="font-weight: 400"></</span><span style="font-weight: 400">LogContext.Consumer</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">       </span><span style="font-weight: 400"></</span><span style="font-weight: 400">Log</span><span style="font-weight: 400">></span>
<span style="font-weight: 400">   );</span>
<span style="font-weight: 400">}</span>

The first thing we do in the LogEvent component is check if <span style="font-weight: 400">logEventProps</span> and <span style="font-weight: 400">actionProps</span> are present. Since our logging functionality requires <span style="font-weight: 400">logEventProps</span> and <span style="font-weight: 400">actionProps</span> to work, if either is missing, we simply return the unmodified child component (e.g. button element). 

If the required props exist, we wrap our entire LogEvent in a Log component. Why? We want our Log component to inherit data tag props from parent Log components via the Context API and combine them with the <span style="font-weight: 400">elementType</span> and other <span style="font-weight: 400">logEventProps</span> passed into LogEvent. This eliminates the need for data tag copying and provides all the contextual data tags for our event log. Also, if we want to log an impression of the child component (e.g., the button), we can do that by passing the logImpression flag in <span style="font-weight: 400">logEventProps</span>.

Once Log has created the data props object, we need to consume it with <span style="font-weight: 400">LogContext.Consumer</span>. Now, we have all the data tags we need!  

Next, we address the fact that the children prop is plural. In our example, we wrapped just one element, a button. But, we may wrap multiple components with LogEvent (e.g. radio button group). Thus, we use React.Children.map to iterate and return the wrapped child or children. We don’t want to simply return an unmodified wrapped child. What we want to do is inject our logging code into the child’s event handler! For that, we are going to use React.cloneElement

The <span style="font-weight: 400">cloneElement</span> function returns a copy of an element. You can use it to add or modify the copied element’s props. Perfect! Since we want to add logging code to the child’s event handler prop, we need to modify the handler and pass it to <span style="font-weight: 400">cloneElement</span>. Our <span style="font-weight: 400">getEventHandlers</span> function contains our modification code:

<span style="font-weight: 400">function</span> <span style="font-weight: 400">getEventHandlers</span><span style="font-weight: 400">(</span><span style="font-weight: 400">child</span><span style="font-weight: 400">, </span><span style="font-weight: 400">consumedProps</span><span style="font-weight: 400">, </span><span style="font-weight: 400">actionProps</span><span style="font-weight: 400">) {</span>
<span style="font-weight: 400">   </span><span style="font-weight: 400">const</span> <span style="font-weight: 400">eventHandlers</span><span style="font-weight: 400"> = {};</span>
<span style="font-weight: 400">   </span><span style="font-weight: 400">forEach</span><span style="font-weight: 400">(</span><span style="font-weight: 400">actionProps</span><span style="font-weight: 400">, (</span><span style="font-weight: 400">specificHandlerProps</span><span style="font-weight: 400">, </span><span style="font-weight: 400">eventHandlerName</span><span style="font-weight: 400">) </span><span style="font-weight: 400">=></span><span style="font-weight: 400"> {</span>
<span style="font-weight: 400">       </span><span style="font-weight: 400">eventHandlers</span><span style="font-weight: 400">[</span><span style="font-weight: 400">eventHandlerName</span><span style="font-weight: 400">] = (...</span><span style="font-weight: 400">eventHandlerArgs</span><span style="font-weight: 400">) </span><span style="font-weight: 400">=></span><span style="font-weight: 400"> {</span>
<span style="font-weight: 400">           </span><span style="font-weight: 400">child</span><span style="font-weight: 400">.</span><span style="font-weight: 400">props</span><span style="font-weight: 400">[</span><span style="font-weight: 400">eventHandlerName</span><span style="font-weight: 400">]?.</span><span style="font-weight: 400">apply</span><span style="font-weight: 400">(</span><span style="font-weight: 400">child</span><span style="font-weight: 400">, </span><span style="font-weight: 400">eventHandlerArgs</span><span style="font-weight: 400">);</span>
<span style="font-weight: 400">           </span><span style="font-weight: 400">sendLog</span><span style="font-weight: 400">({ ...</span><span style="font-weight: 400">consumedProps</span><span style="font-weight: 400">, ...</span><span style="font-weight: 400">specificHandlerProps</span><span style="font-weight: 400"> });</span>
<span style="font-weight: 400">       };</span>
<span style="font-weight: 400">   });</span>

<span style="font-weight: 400">   </span><span style="font-weight: 400">return</span> <span style="font-weight: 400">eventHandlers</span><span style="font-weight: 400">;</span>
<span style="font-weight: 400">}</span>

In <span style="font-weight: 400">getEventHandlers</span>, we initialize an empty <span style="font-weight: 400">eventHandlers</span> object. It will store all the event handler functions we will modify. For our example, in LogEvent’s <span style="font-weight: 400">actionProps</span>, we specified a single event handler prop function’s name, <span style="font-weight: 400">onClick</span>. In some cases however, we may have multiple event handlers we may want to modify. Therefore, we loop through each of our <span style="font-weight: 400">actionProps</span>

The key of each of the <span style="font-weight: 400">actionProps</span> is the event handler name (e.g. <span style="font-weight: 400">onClick</span>). The value is an object containing additional data tags specific to that event handler. In our example, the value for <span style="font-weight: 400">specificHandlerProps</span> is <span style="font-weight: 400">{ action: 'click' }</span>. In our loop, we use the JS apply function to ensure that the cloned child’s original event handler still gets called with all of its arguments. After all, we want our close button to still close the promo banner! Finally, we also add our <span style="font-weight: 400">sendLog</span> call with the data tags from our consumed props and specific handler props. 

Now that we’re done, when someone clicks our close button, it will close the promo banner and log all of the data we want. Eureka! Also, since we’ve wrapped our Button component, it is ready for easy-to-use logging anywhere in our codebase. 

Thanks to LogEvent there is no more error-prone data copying for each logged event or mixing substantive code with logging code in event handlers. We can also easily use LogEvent to wrap other action components like selects and inputs by passing in different <span style="font-weight: 400">actionProps</span>!

By making it easier to implement impressions and event logging, we saw a 66% decrease in log code length. Also, it takes devs a quarter of the time to implement logs.

Part II Coming Soon

In the next installment, we will examine how we abstracted the library for use by any team. We will also look at how we added two powerful features: keeping log history and building a live log viewer. Finally, we will discuss the library’s adoption and impact at Slack. Stay tuned!