skip to Main Content

Problem Overview:

I’m trying to add labels to values inside an array property of an object.

The objects are various Insights fetched from Facebook’s Graph API. We pull 25 different metrics…

There are 3 in particular that I am interested in.
My search criteria: name = 'page_content_activity', period = 'day'/'week'/'days_28'

Here is an example of an object returned from the search:

{
  name: 'page_content_activity',
  period: 'day',
  values: [
    {
      value: 23168,
      end_time: '2020-03-10T07:00:00+0000',
    },
    {
      value: 19309,
      end_time: '2020-03-11T07:00:00+0000',
    },
  ],
  title: 'Daily Page Stories',
  description: 'Daily: The number of stories created about your Page. (Total Count)',
  id: 'PAGE_ID/insights/page_content_activity/day',
}

Objective

The labels I want to add to object in values are ‘Today’ and ‘Yesterday’ giving me:

{
...
  values: [
    {
      value: 23168,
      end_time: '2020-03-10T07:00:00+0000',
      label: 'Today'
    },
    {
      value: 19309,
      end_time: '2020-03-11T07:00:00+0000',
      label: 'Yesterday'
    },
  ],
...
}

I want to repeat this process with each of the other two time periods ‘week’ and ‘days_28’ with the labels to be added ‘This Week’, ‘Last Week’ and ‘Last 28 Days’, ‘Previous 28 Days’ respectively.


My Solution:

  1. I created the below array of nested arrays each with the period value first to be used in the search and another array of the two labels to be added
const periods = [
  ['day', ['Today', 'Yesterday']],
  ['week', ['This Week', 'Last Week']],
  ['days_28', ['Last 28 Days', 'Prev 28 Days']],
]
  1. Here is my function to find the objects, append the labels, and then render the list items:
const PageStats = ({ insights, page }) => {
  if (!page && !insights) return

  // This is the function in question
  const renderActivity = () => {
    const name = 'page_content_activity'
    let activity = []
    periods.forEach(period => {
      let data = insights.data.find(insight => {
        return insight.name === name && insight.period === period[0]
      })
      // Here is where I am adding the label to each object in 'values' array
      data.values[0].label = period[1][0]
      data.values[1].label = period[1][1]
      activity.push(data.values)
    })
    // Finally I map thru activity array and map thru each period array to 
    // render a list item that displays the newly added label and corresponding value
    return activity.map(period => {
      return period.map(item => renderItem(item))
    })
  }

  return (
    <Segment raised placeholder>
      {renderLabel(`${page.name} - ${page.category}`)}
      <Header content="Total Activity - Over Time" />
      <List horizontal>{renderActivity()}</List>
    </Segment>
  )
}

I know that chaining map() is not advised but it’s what I came up with that worked and it’s a small data set that won’t get larger over time…


My Question:

I spent a long time on this and tried different methods for representing the period variable and the corresponding labels. I had them in an array but as objects then used Object.entries(period).map(... but it started getting ugly and I finally settled on this.

I am curious to see what other solutions are possible? I’d like to know the shortest most efficient method for doing something of this nature. I look forward to seeing your responses!

Thanks

2

Answers


  1. There are three primary pain points that I’d want to address:

    1. Minimizing the data search.
    2. Removing the double mapping.
    3. Deconstructing the arrays for readability.

    The first optimization requires modifying your dataset. Your current limitation is that you have two separate keys you’re searching. If you can combine them into a single key, you can convert your insights from an array to an object:

    insights.data = {
      page_content_activity_day: {
        name: 'page_content_activity',
        period: 'day',
        values: [
          {
            value: 23168,
            end_time: '2020-03-10T07:00:00+0000',
          },
          {
            value: 19309,
            end_time: '2020-03-11T07:00:00+0000',
          },
        ],
        title: 'Daily Page Stories',
        description: 'Daily: The number of stories created about your Page. (Total Count)',
        id: '297134493721424/insights/page_content_activity/day',
      },
      ...
    }
    

    This would let you remove your find method. If you are doing this multiple times, you may also find it to be marginally faster, since you make one loop to create the keys instead of 3 to find them.

    The second optimization recognizes that your forEach function is creating a 1:1 mapping of periods to objects. Rather than looping twice, one to create objects and one to render them, you can combine the two.

    The third optimization is deconstructing your arrays, so you can give meaningful names to your elements.

    This results in:

    const renderActivity = () => {
      const name = 'page_content_activity'
      return periods.map(([period, [currentLabel, priorLabel]]) => {
        let [currentValue, priorValue] = insights.data[`${name}_${period}`].values
        currentValue.label = currentLabel
        priorValue.label = priorLabel
        return renderItem([currentValue, priorValue])
      }
    }
    

    I’m recreating the values array in renderItem, but this seems a negligible cost for code that feels more readable to me. It could certainly be kept as values[].

    If you had created an activities array because you weren’t certain there would always be data for a given combination of keys, you could add an if condition that returns null if values doesn’t exist. Your final map will include null values when data doesn’t exist, but React will ignore these. That makes it harder to deconstruct the values, which makes me sad, but it would look like:

    const renderActivity = () => {
      const name = 'page_content_activity'
      return periods.map(([period, [currentLabel, priorLabel]]) => {
        let values = insights.data[`${name}_${period}`]?.values
    
        if (!values) {
          return null
        }
    
        values[0].label = currentLabel
        values[1].label = priorLabel
        return renderItem(values)
      }
    }
    

    (The optional chaining operator ?. allows you to get undefined instead of an error if the underlying object is undefined.)

    Login or Signup to reply.
  2. 1) Build periodsObject so that can avoid multiple find calls

    2) Use filter and map methods to simplify the required output.

    const insights = [
      {
        name: "page_content_activity",
        period: "day",
        values: [
          {
            value: 23168,
            end_time: "2020-03-10T07:00:00+0000"
          },
          {
            value: 19309,
            end_time: "2020-03-11T07:00:00+0000"
          }
        ],
        title: "Daily Page Stories",
        description:
          "Daily: The number of stories created about your Page. (Total Count)",
        id: "297134493721424/insights/page_content_activity/day"
      }
    ];
    
    const periods = [
      ["day", ["Today", "Yesterday"]],
      ["week", ["This Week", "Last Week"]],
      ["days_28", ["Last 28 Days", "Prev 28 Days"]]
    ];
    
    const renderItem = item => console.log(item);
    
    const periodsObject = Object.fromEntries(periods);
    
    const renderActivity = () => {
      const name = "page_content_activity";
      return insights
        .filter(
          insight =>
            insight.name === name &&
            Object.keys(periodsObject).includes(insight.period)
        )
        .map(insight => ({
          ...insight,
          values: insight.values.map((value, i) =>
            renderItem({ ...value, label: periodsObject[insight.period][i] })
          )
        }));
    };
    
    renderActivity();
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search