How to build a marketing dashboard (twitter & youtube) with React
Tags: react, twitter, beginners, tutorial
Published: 2021-12-11 22:36:59

Integrations play a huge role in communicating across different services. In this guide, we'll build a marketing analytical dashboard with **Twitter** and **Youtube** integrations. This dashboard can be used to track stats across both platforms in a single view. 

We're looking to build something like this 👇

![Final Project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o37jts3jq3cjstupf27c.gif)
  

We'll be using:

- [**React**](https://reactjs.org/) for building the frontend.
- [**Blueprint**](https://blueprintjs.com/) as our UI Library
- [**Backend template**](https://app.canonic.dev/projects/61b4e0b47bfc8f00268a7f9e/graph?utm_source=dev.to&utm_medium=organic_article&utm_campaign=30daysofcanonic_uc_2&utm_term=header_backend_template&utm_content=day_13) from Canonic
- [**GraphQL**](https://graphql.org/) for fetching the data.
- [**React-chartjs-2**](https://github.com/reactchartjs/react-chartjs-2) for building charts.

### Lets get started! 🚀

**Step 1: Getting started with the project**

To create the project, we'll use `create-react-app`. Execute the command below in a terminal to create the basic boilerplate setup. We'll name our project `marketing-dashboard`.

```powershell
npx create-react-app marketing-dashboard
```

**Step 2: Add the dependencies**

Let's add all the dependencies listed above for ui and GraphQL. Navigate to project folder, and start adding the dependencices.

```powershell
yarn add @blueprintjs/core @apollo/client graphql@15.7.1 react-chartjs-2 chart.js
```

Now let's discuss the project setup. We'll have three top level component setups:

1. Header
2. Middle Component - Further divided into two components - Twitter & Youtube
3. Footer

**Step 3: Building top level components**

Let's first modify `App.js` to accommodate for our top level components. We'll add the `Header` and `Footer` components directly in the `App` and create space for `Twitter` and `Youtube` components. While we're at it, might as well add some styling :P

```jsx
// Import React and Blueprint dependencies
import React from "react";

import "@blueprintjs/core/lib/css/blueprint.css";
import { Navbar, Alignment, Divider } from "@blueprintjs/core";

import "./App.css";

const App = () => {
  return (
    <div className="App">
      {/* Header */}
      <Navbar className="bp3-dark" style={{ position: "fixed", top: "0" }}>
        <Navbar.Group align={Alignment.LEFT}>
          <Navbar.Heading style={{ marginLeft: "30px" }}>
            Marketing Dashboard
          </Navbar.Heading>
        </Navbar.Group>
      </Navbar>
      {/* Middle Container to hold our Metrics */}
      <div
        style={{ marginLeft: "30px", marginTop: "80px", marginRight: "30px" }}
      >
        <div style={{ marginTop: "50px" }}>
         {/* Youtube component will go here */}
        </div>
        <Divider style={{ marginTop: "50px" }}></Divider>
        <div style={{ marginTop: "50px" }}>
          {/* Twitter component will go here */}
        </div>
        <Divider style={{ marginTop: "50px" }}></Divider>
      </div>
      {/* Footer */}
      <div>
        <h5
          style={{
            fontSize: "medium",
            textAlign: " left",
            margin: "30px 30px 20px 30px",
          }}
        >
          Canonic ©2021 Created by Canonic Inc
        </h5>
      </div>
    </div>
  );
};

export default App;
```

Remove all of the preloaded CSS in `App.css` file.

**Step 4: Mocking data to display**

We'll create dummy data that mimics the API response and feeds directly into our `Twitter` and `Youtube` components.

Let's create `dummyData.js` at `src` directory and add the following dummy data containing metrics for both the platforms.

```jsx
export const dummyData = {
  metric: {
    youtubeMetrics: {
      rows: [[745, 2, 16, 1, 597, 48]],
      columnHeaders: [
        {
          columnType: "METRIC",
          dataType: "INTEGER",
          name: "views",
        },
        {
          columnType: "METRIC",
          dataType: "INTEGER",
          name: "comments",
        },
        {
          columnType: "METRIC",
          dataType: "INTEGER",
          name: "likes",
        },
        {
          columnType: "METRIC",
          dataType: "INTEGER",
          name: "dislikes",
        },
        {
          columnType: "METRIC",
          dataType: "INTEGER",
          name: "estimatedMinutesWatched",
        },
        {
          columnType: "METRIC",
          dataType: "INTEGER",
          name: "averageViewDuration",
        },
      ],
    },
    youtubeMinutesDay: {
      rows: [
        ["2020-10-17", 0],
        ["2020-10-18", 1],
        ["2020-10-19", 4],
        ["2020-10-20", 0],
        ["2020-10-21", 4],
        ["2020-10-22", 4],
        ["2020-10-23", 1],
        ["2020-10-24", 4],
        ["2020-10-25", 0],
        ["2020-10-26", 1],
        ["2020-10-27", 0],
        ["2020-10-28", 0],
        ["2020-10-29", 0],
        ["2020-10-30", 1],
        ["2020-10-31", 0],
        ["2020-11-01", 1],
        ["2020-11-02", 1],
        ["2020-11-03", 2],
        ["2020-11-04", 5],
        ["2020-11-05", 0],
        ["2020-11-06", 1],
        ["2020-11-07", 0],
        ["2020-11-08", 5],
        ["2020-11-09", 3],
      ],
      columnHeaders: [
        {
          columnType: "DIMENSION",
          dataType: "STRING",
          name: "day",
        },
        {
          columnType: "METRIC",
          dataType: "INTEGER",
          name: "estimatedMinutesWatched",
        },
      ],
    },
    youtubeMinutesCountry: {
      rows: [
        ["IN", 30],
        ["US", 12],
      ],
      columnHeaders: [
        {
          columnType: "DIMENSION",
          dataType: "STRING",
          name: "country",
        },
        {
          columnType: "METRIC",
          dataType: "INTEGER",
          name: "estimatedMinutesWatched",
        },
      ],
    },
    twitter: {
      data: {
        name: "Canonic",
        username: "CanonicHQ",
        profile_image_url:
          "https://pbs.twimg.com/profile_images/1316281577148571653/-12Ans7U_normal.jpg",
        location: "United States",
        description:
          "The lowcode backend to your frontend. Ranked #3 Product of the day on @producthunt #lowcode #nocode #reactjs #graphql #technology #automation #backend",
        public_metrics: {
          followers_count: 394,
          following_count: 641,
          tweet_count: 161,
          listed_count: 25,
        },
      },
    },
  },
};
```

When we're feeding this data into our components, we need to normalise it so that we can display the data efficiently. To achieve this, we'll create utility functions that will transform this data into a more readable format.

Create a new directory - `utils` at `src/` and a new file inside `src/utils/normaliseData.js`. Inside `normaliseData.js` we'll  add functions to convert normalise youtube data, twitter data, as well as data for our charts.

```jsx
export const normaliseTwitterData = (twitter) => {
  if (Object.keys(twitter).length === 0) {
    return { accountInformation: {}, metrics: [] };
  }

  const twitterData = twitter.twitter;
  return {
    accountInformation: {
      title: "Account Information",
      username: `@${twitterData.username}`,
      description: twitterData.description,
      location: twitterData.location,
    },
    metrics: [
      {
        title: "Followers",
        description: twitterData.public_metrics.followers_count,
      },
      {
        title: "Following",
        description: twitterData.public_metrics.following_count,
      },
      {
        title: "Tweets",
        description: twitterData.public_metrics.tweet_count,
      },
      {
        title: "Listed",
        description: twitterData.public_metrics.listed_count,
      },
    ],
  };
};

export const normaliseYoutubeMetrics = (youtubeData) => {
  return (
    youtubeData.columnHeaders.map((column, index) => {
      return {
        title: camelCaseToSentenceCase(column.name),
        description: youtubeData.rows[0][index],
      };
    }) || []
  );
};

export const normaliseEstimatedMinutesWatched = (estimatedMinutes) => {
  const labels =
    estimatedMinutes.rows.map((row) => {
      return row[0];
    }) || [];

  const data = estimatedMinutes.rows.map((row) => {
    return `${row[1]}`;
  });

  return {
    labels,
    datasets: [
      {
        label: "Minutes Watched / Day",
        data,
        fill: true,
        backgroundColor: "rgba(53, 162, 235, 0.5)",
        borderColor: "rgb(53, 162, 235)",
      },
    ],
  };
};

export const normaliseEstimatedMinutesPerCountry = (
  estimatedMinutesCountry
) => {
  const labels =
    estimatedMinutesCountry.rows.map((row) => {
      return row[0];
    }) || [];

  const data = estimatedMinutesCountry.rows.map((row) => {
    return `${row[1]}`;
  });

  return {
    labels,
    datasets: [
      {
        label: "Minutes Watched / Country",
        data,
        fill: true,
        backgroundColor: "rgba(255, 99, 132, 0.5)",
      },
    ],
  };
};

const camelCaseToSentenceCase = (text) => {
  const result = text.replace(/([A-Z])/g, " $1");
  return result.charAt(0).toUpperCase() + result.slice(1);
};
```

**Step 5: Creating Twitter and Youtube components**

Let's create our final two components required - Twitter and Youtube. We'll create a card shaped dashboard.
Create a `components` directory, and add `Twitter.js` and `Youtube.js` inside their respective folders. Let's add code!!

src/components/Twitter/Twitter.js

```jsx
import React from "react";

import { Card, Elevation } from "@blueprintjs/core";
import { normaliseTwitterData } from "../../utils/normaliseData";

const Twitter = (twitter = {}) => {
  const twitterData = normaliseTwitterData(twitter);
  return (
    <div>
      <h5 style={{ fontSize: "large", textAlign: " left" }}>Twitter</h5>
      <div
        style={{
          display: "flex",
          flexWrap: "wrap",
          justifyContent: "space-between",
          gap: "10px",
        }}
      >
        <Card
          interactive={true}
          elevation={Elevation.TWO}
          style={{
            minWidth: "200px",
            minHeight: "200px",
            maxWidth: "500px",
          }}
        >
          <h5 style={{ fontSize: "large", color: "#394b59" }}>
            {twitterData.accountInformation.title}
          </h5>
          <div style={{ fontSize: "medium" }}>
            <p>{twitterData.accountInformation.username}</p>
            <p>{twitterData.accountInformation.description}</p>
            <p>{twitterData.accountInformation.location}</p>
          </div>
        </Card>
        {twitterData?.metrics.map((card) => {
          return (
            <Card
              interactive={true}
              elevation={Elevation.TWO}
              style={{
                minWidth: "200px",
                minHeight: "200px",
              }}
              key={card.title}
            >
              <h5 style={{ fontSize: "large", color: "#394b59" }}>
                {card.title}
              </h5>
              <p style={{ fontSize: "xx-large" }}>{card.description}</p>
            </Card>
          );
        })}
      </div>
    </div>
  );
};

export default Twitter;
```

src/components/Twitter/index.js

```jsx
export { default } from "./Twitter.js";
```

Similarly, adding to the youtube component.

src/components/Youtube/Youtube.js

```jsx
import React from "react";

import { Card, Elevation } from "@blueprintjs/core";
import Chart from "chart.js/auto";
import { Line, Bar } from "react-chartjs-2";
import {
  normaliseYoutubeMetrics,
  normaliseEstimatedMinutesWatched,
  normaliseEstimatedMinutesPerCountry,
} from "../../utils/normaliseData";

const Youtube = ({
  youtubeMetrics = {},
  youtubeMinutesDay = {},
  youtubeMinutesCountry = {},
}) => {
  const youtubeData = normaliseYoutubeMetrics(youtubeMetrics);
  const estimatedMinutesWatched =
    normaliseEstimatedMinutesWatched(youtubeMinutesDay);
  const estimatedMinutesWatchedCountry = normaliseEstimatedMinutesPerCountry(
    youtubeMinutesCountry
  );
  return (
    <div>
      <h5 style={{ fontSize: "large", textAlign: " left" }}>Youtube</h5>
      <div
        style={{
          display: "flex",
          flexWrap: "wrap",
          justifyContent: "space-between",
          gap: "10px",
        }}
      >
        {youtubeData?.map((card) => {
          return (
            <Card
              interactive={true}
              elevation={Elevation.TWO}
              style={{
                minWidth: "200px",
                minHeight: "200px",
              }}
              key={card.title}
            >
              <h5 style={{ fontSize: "large", color: "#394b59" }}>
                {card.title}
              </h5>
              <p style={{ fontSize: "xx-large" }}>{card.description}</p>
            </Card>
          );
        })}
        <Line
          data={estimatedMinutesWatched}
          style={{ maxHeight: "60%", maxWidth: "47%", marginTop: "10px" }}
        />
        <Bar
          data={estimatedMinutesWatchedCountry}
          style={{ maxHeight: "60%", maxWidth: "47%", marginTop: "10px" }}
        />
      </div>
    </div>
  );
};

export default Youtube;
```

src/components/Youtube/index.js

```jsx
export { default } from "./Youtube.js";
```

Let's make a few changes to `App.js` and add in the dummy data to get this all up and running!

src/App.js

```jsx
...

import Twitter from "./components/Twitter";
import Youtube from "./components/Youtube";

import { dummyData } from "./dummyData";

...

const App = () => {
  const { metric = {} } = dummyData;

  return (
    <div className="App">
      ...
      {/* Middle Container to hold our Metrics */}
      <div
        style={{ marginLeft: "30px", marginTop: "80px", marginRight: "30px" }}
      >
        <div style={{ marginTop: "50px" }}>
          <Youtube
            youtubeMetrics={metric.youtubeMetrics}
            youtubeMinutesDay={metric.youtubeMinutesDay}
            youtubeMinutesCountry={metric.youtubeMinutesCountry}
          ></Youtube>
        </div>
        <Divider style={{ marginTop: "50px" }}></Divider>
        <div style={{ marginTop: "50px" }}>
          <Twitter twitter={metric.twitter.data}></Twitter>
        </div>
        <Divider style={{ marginTop: "50px" }}></Divider>
      </div>
      {/* Footer */}
      ...
   ...
...
```

It should look like this

![Step 5](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i7u61cd7ghe232cd1rmm.png)
 

**Step 6: Getting the backend APIs**

Let's head to ***Canonic*** and clone this **[template](https://app.canonic.dev/projects/61b4e0b47bfc8f00268a7f9e/graph?utm_source=dev.to&utm_medium=organic_article&utm_campaign=30daysofcanonic_uc_2&utm_term=step6_template&utm_content=day_13)** to get started. It already has all of the setup required for youtube and twitter integrations. You can either:

- Use this [sample project](https://app.canonic.dev/projects/61b4e0b47bfc8f00268a7f9e/graph?utm_source=dev.to&utm_medium=organic_article&utm_campaign=30daysofcanonic_uc_2&utm_term=step6_sample_project&utm_content=day_13) to and continue, or
- Clone it, replace the **Twitter** & **Youtube** credentials with yours and hit Deploy 🚀 . This will then use your data from the services to fetch.
    
    ![Step 6](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kw548y9ba6weok1zfvfa.png)
 
    

> You can see the **GraphQL API Endpoint** in the **Docs Section** of your project
> 

**Step 7: Configuring GraphQL**

We are using `ApolloClient` to connect with our backend through GraphQL. Head to `src/index.js` and:

- Import the new dependencies
- Configure `ApolloClient`
- Wrap our top component with `ApolloProvider`

```jsx
...
import { ApolloProvider, InMemoryCache, ApolloClient } from "@apollo/client";

/**
 * Connecting with the backend using apollo client
 */
const client = new ApolloClient({
  // Make sure you update the URI here to point to your backend
  uri: "https://marketing-dashboard.can.canonic.dev/graphql",
  cache: new InMemoryCache({
    addTypename: false,
  }),
});

ReactDOM.render(
  <React.StrictMode>
    <ApolloProvider client={client}>
      <App />
    </ApolloProvider>
  </React.StrictMode>,
  document.getElementById("root")
);
```

> Replace the `uri` with your GraphQL API Endpoint if you cloned and deployed your own project.
> 

**Step 8: Configuring GraphQL queries to fetch data**

After setting up our client and backend, you can always head to **Docs** tab to get more information about the API. It has a nice layout that lets you explore docs for your APIs.

Create a javascript file at `src/gql/query.js` and add the following code.

```jsx
import { gql } from "@apollo/client";

/**
 * gql query to get customers information
 * The query parameters we got straight from Canonic autogenerated documentation
 */
export const GET_MARKETING_METRICS = gql`
  query {
    metric {
      youtubeMetrics {
        rows
        columnHeaders {
          columnType
          dataType
          name
        }
      }
      youtubeMinutesDay {
        rows
        columnHeaders {
          columnType
          dataType
          name
        }
      }
      youtubeMinutesCountry {
        rows
        columnHeaders {
          columnType
          dataType
          name
        }
      }
      twitter {
        data {
          name
          username
          profile_image_url
          location
          description
          public_metrics {
            followers_count
            following_count
            tweet_count
            listed_count
          }
        }
      }
    }
  }
`;
```

**Step 9: Executing the query**

Let's head to `App.js` for one final time. We'll execute our API and pass the data to our components so that they can display the actual numbers. Since we've already setup the dummy data similar to our API response, this will be just like flipping a switch, and we'll be done.

src/App.js

```jsx
...
import { Navbar, Alignment, Divider, Spinner } from "@blueprintjs/core";

import { useQuery } from "@apollo/client";
import { GET_MARKETING_METRICS } from "./gql/query";

...

const App = () => {
  const { data = {}, loading } = useQuery(GET_MARKETING_METRICS);
  const { metric = {} } = data.metric ? data : dummyData;

  if (loading)
    return (
      <div style={{ margin: "200px" }}>
        <Spinner></Spinner>
      </div>
    );

  return (
    <div className="App">
      ...
    </div>
  );
};

export default App;
```

and **Voila!** We're done with the entire integration. Now if you run your project with `yarn start` it should show like this:

![Final](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xojxh4o5b5mgx7f2r1ux.png)
 

**[Live Link](https://canonic-marketing-dashboard.netlify.app/?utm_source=dev.to&utm_medium=organic_article&utm_campaign=30daysofcanonic_uc_2&utm_term=conclusion_live_demo&utm_content=day_13)**

**[Sample code on GitHub](https://github.com/canonic-dev/canonic-samples#marketing-dashboard)**

## Conclusion

Hope this guide helped you gain better insight about creating a dashboard that fetches data from **Twitter** and **Youtube**, how you can structure it, and how to quickly get a basic dashboard up and running. You can also check out our other guides [here](https://dev.to/canonic/).

Join us on [discord](https://discord.gg/9dyytsM) to discuss or share with our community. Write to us for any support requests at [support@canonic.dev](https://support@canonic.dev/). Check out our [website](https://www.canonic.dev/30-days-of-canonic/?utm_source=dev.to&utm_medium=organic_article&utm_campaign=30daysofcanonic_uc_2&utm_term=conclusion_website&utm_content=day_13) to know more about Canonic.