I’m now a few weeks in building my first React Web App called Nomad Wifi and last week, I came across an interesting problem.

Currently within Nomad Wifi, I’m working with three models (not including Users). These are:

  1. Destinations: Cities, Towns or Islands
  2. Locations: Cafes, Coworking Spaces and anywhere else with public Wifi, and
  3. Networks: These are the actual Wifi networks, complete with SSID and a list of speeds

I want to be able to find a list of speeds and calculate averages on the Destination level down to the Network level.

I can do this with the following function that sits within my Map component.


speeds_for_location(location) {
    return location.networks.map((network) {
      return network.speed_tests.map((speed_test) {
         return speed_test.down_speed;
      });
    });
  }

With this function, I can pass in any location object and get back an array of speeds.

Let’s say I want to get the average speed for the Google Campus in London…

const speeds_for_google_campus = this.speeds_for_location(google_campus); // => [117.3, 71.11]

const average_down_speed = _.mean(speeds_for_google_campus); // => 94.205

Currently, this function is just what I’m after but here’s the problem: I need to be able to gather the average speed for Destinations also.

I wondered… Is it possible to abstract that function into a helper file like you would in Rails for example?

Here’s where I started to do some research and came across two solutions

Approach 1: Abstract the Function to a Higher-Order Component

The first solution I discovered was by Carl Sverre via this StackOverflow question.

His suggestion: create a higher-order component and put your functions as methods on the class.

For React newbies, a Higher-Order component is a pattern in React where you can wrap common functionality around a component to ‘compose’ a new component – or as Facebook puts it: a higher-order component is a function that takes a component and returns a new component. learn more about Higher Order components here.

So, I created a higher-order component which I named Helpers.js.

// Helpers.js
import React, { Component } from 'react';

export default function(ComposedComponent) {
  class withHelpers extends Component {

    speeds_for_location(location) {
        return location.networks.map((network) => {
          return network.speed_tests.map((speed_test) => {
            return speed_test.down_speed;
          });
        });
      }

    render() {
      return <ComposedComponent {...this.props} />
    }
  }

  return withHelpers;
}

I decided to name the class ‘withHelpers’ as I felt it more descriptive given the pattern of ‘composing the component’.

Within my DestinationDetail component, I compose the map component with the helpers.

// DestinationDetail.js

// Import Dependencies
import Map from '../components/Map';
import withHelpers from '../components/Helpers';

// Compose the component
const MapWithHelpers = withHelpers(Map);

Now, when I use the component in the DestinationDetail component, I replace <Map /> with <MapWithHelpers />

The result: I can now call this.speeds_for_location(location) in my Map component as if it was a method on the class. – Or so I thought…

While I was able to call the function in my render() and other such methods, it turns out that from within my .map function inside my ComponentWillRecieveProps method, I no longer had access to the function.

// Map.js
componentWillReceiveProps(nextProps) {

    console.log(speeds_for_location); // => returns the function

    var markerBounds = new google.maps.LatLngBounds();

    // Display Markers
    return nextProps.locations.map((location) => {
       console.log(speeds_for_location); // => returns undefined
    }

I decided to try a different approach, one that seemed a lot cleaner.

Approach 2: Abstract the Function to its own File

Approach 1 may have seemed to be enough to abstract my function out of the deeply nested Map component but it seemed a bit of overkill to have to create a whole new class just to house a few functions.

Surely I could put the function (alongside any others) in a basic file with just the functions and nothing else, as is the pattern in frameworks like Ruby on Rails, right?

I went back to Google and found this solution by Patrick Gordon, who coincidentally enough helped me with a Ruby on Rails issue back in 2015.

With this approach, I cleared out the withHelpers component in my Helper.js file and replaced it with the following lines:

// Helpers.js
export function speeds_for_location(location) {
    return location.networks.map((network) => {
      return network.speed_tests.map((speed_test) => {
        return speed_test.down_speed;
      });
    });
  }

With the higher order component now gone, I refactored my code, removing the composition of the MapWithHelpers component and removed this from the function call as the function is now no longer a method on the class.

Also, I was able to remove the reference to Helpers in the DestinationDetail component and move it directly into the Map component.

Now that the function has been declared and exported, I can just import it like in the example below and use it within my class methods right? – Not quite.

import {speeds_for_location} from './Helpers';

After importing the function with the above code, I found I was able to use the function within my class but once again, when I started to loop through the various locations using javascript’s .map function, I no longer had access.

// Map.js
componentWillReceiveProps(nextProps) {

    console.log(speeds_for_location); // => returns the function

    var markerBounds = new google.maps.LatLngBounds();

    // Display Markers
    return nextProps.locations.map((location) => {
       console.log(speeds_for_location); // => returns undefined
    }

Suddenly, in the context of nextProps.locations.map(location) I had no access to my imported function and I was forced to make it a class method with some very redundant-looking code.

// Map.js
class Map extends Component {
  
  speeds_for_location(location) {
    return speeds_for_location(location);
  }
  ...
}

I felt like I’d come full circle. I started with the higher-order component approach but felt that a composed component was overkill and now had to still create a class method even after importing the function in order to use it with in my map function.

Approach 3: Passing Functions as Props

After getting neither of the two previous approaches to work, I resorted to passing the function down as property on Map component after exporting the function in my Helpers.js file and importing it into the DestinationDetail component.

// Import Statement
import {speeds_for_location} from '../components/Helpers';

...

// Within my renderDestination method






<Map locations={destination.locations} speeds_for_location={speeds_for_location}/>;

I figured that perhaps the issue I was experiencing was because I nested a lot of the code in the componentWillReceiveProps lifecycle method. It felt like passing in the function as an argument to this method was my only shot at getting this method to acknowledge my function and it of course, it worked.

Now, I take the function right off the nextProps argument and use it within my .map function, as I’ve done below.

componentWillReceiveProps(nextProps) {

    var markerBounds = new google.maps.LatLngBounds();
    // Display Markers
    return nextProps.locations.map((location) => {

      const speeds_for_location = nextProps.speeds_for_location(location);

     ...

Conclusion

In the end, I found that in my particular case, I had to pass in the function as a property on the component.

However, the second approach seems like the simpler approach and definitely the one I’m more used to.

Unless I find a cleaner way to make use of that function within a function of a class method, I’m going to stick with passing in the function but what are your thoughts?

Am I following best practices? Let me know in the comments below.

Thanks for reading,
Chris