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:
- 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']],
]
- 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
There are three primary pain points that I’d want to address:
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:
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:
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 asvalues[]
.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 returnsnull
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:(The optional chaining operator
?.
allows you to get undefined instead of an error if the underlying object is undefined.)1) Build
periodsObject
so that can avoid multiplefind
calls2) Use
filter
andmap
methods to simplify the required output.