DEV Community

Agbanusi John
Agbanusi John

Posted on

freeCodeCamp take home project 3 - Use the twitch API

Now we will talk about the third project and how to go about it.
We're basically asked to use the twitch API to get some general information about some twitch users and display it in a user friendly and personalized way.

Project: Twitch API,
Time taken: 5 hours,
Difficulty: easy.
The link to the take home project is here.

Now, this is a very simple project but with one pitfall, which requires using two fetch requests per user. You might say what's wrong with that, just use them one after each other and zoom, you're done! But after doing this you'll discover that this takes a lot of time in getting the response needed, sometimes up to 10 minutes just to load your pages, which could become troublesome for impatient users.

So, what can we do? Well, JavaScript provides us with asynchronous functions. This helps in paralleling of codes, meaning while this code is running it goes to the next one without waiting for the other to run finish, so in a time frame, you can have two or more codes running at the same time, cutting the runtime exponentially, awesome right? We'll use async functions in this example mainly to help parallel the fetch requests. Let's get to it!

var list=["freecodecamp", "RobotCaleb", "OgamingSC2", "noobs2ninjas"] //list of users
var classer1='v' //change class1
var classer2='v'  //change class2
var classer3='v'  //change class3
var alli=[]  //a global var to extract the values gotten from the all function

We curated a list of users that we'll be getting their general data from twitch. Looking at this, we have four users, and we want two fetch request for each meaning 8 fetch request underway, that could take a while, how can we just shorten it to half or more? Let's go on.

class Main extends React.Component {
  constructor(props){
    super(props);
    //bind functions
    this.streams=this.streams.bind(this);
    this.api_get=this.api_get.bind(this);
    this.compile=this.compile.bind(this);
    this.offline=this.offline.bind(this)
    this.online=this.online.bind(this);
    this.all=this.all.bind(this);
  }
}

The code above just creates a class object and bind the functions we're going to be using to the class object.

Let's go to the first function api_get.

//function to get results from the site through a get request
  api_get(type,channel){
    return fetch('https://wind-bow.glitch.me/twitch-api/'+type+'/'+channel).then(response=>response.json()).then(data=>data)
  }

This function simply creates a get request to the specific site above and returns the response in json format. we don't need to define the function with function functionName(){}, because the function is now a method and now bound to the class object using this.

Next we will create a function that asynchronously gets the two fetch requests per user.

//a function that return the streams and channel data from the get request
  async streams(){
    let stream=[]
    let channel=[]
    for(let i=0; i<list.length;i++){
      stream.push(await this.api_get('streams',list[i]))
      channel.push(await this.api_get('channels',list[i]))
      }
    return {stream,channel}

Breaking it down, a fetch request is asynchronous by nature, so how do we wait for the results to be ready before assigning the result to a variable, we use the await syntax, but it can be only used in an asynchronous function, so we have to define streams as an async function.
So merely looking at the function, we see there are four synchronous and two async operations going on, but is that right? and does that mean we only get half the time? Let's look at it again and we see that the loop actually queues the request, so it runs this.api_get('streams',list[0]) then push to streams --> this.api_get('channels',list[0]) then push to channels and continuously like that for 1,2 to the end of the list. So the synchronous operations does the queuing, then at the end we have 8 async operations running at the same time. So we can assume we'll get our total results in almost 1/8th the time needed for synchronous operations! We don't have to worry about the arrangement in the list because since it a queue, the early ones gets early results most times, so we get an arranged result at the end of the day. So cool right?!, at the end of the function we return an object containing the two results with keys same as their variable names.

Next we have the compile function,

//this gets the total result and compile it into one
  async compile(){
    let st
    for(let i=0; i<1;i++){
      st=await this.streams()
    }
    let stream =st.stream.map((i)=>i.stream)
    let fin=st.channel.map((i,ind)=>{return{...i,stream:stream[ind]}})
    return fin
  }

This waits for the stream function to return its result then merge the two together into one but only taking the results we need, nothing more. Notice it is an async function, know why?
So now we have all we need, now looking at the example given in the challenge, we see you can show all the users, the ones online and also the ones offline. So let's create a function that get the desired data for all the users.

//this injects the data needed to the frontend for all users
  async all(){
    let all=alli.length!==0? alli: await this.compile()
    //
    document.getElementById('channel').innerHTML=''
    all.map((j)=>{
      let text= j.stream? j.game+': '+j.status : 'offline'
      let id= j.stream? 'background-color:#80CBC4' : 'background-color:#FFCDD2'
      document.getElementById('channel').innerHTML+='<div class="card" style='+id+' ><img class="img" src='+j.logo+'><a href='+j.url+'><h3>'+j.display_name+'</h3></a><h5>'+text+'</h5><div><br>'
    })
    alli=all
    classer1='v active'
    classer2='v'
    classer3='v';
  }

This function creates a card for each user and show some data like name, display picture and customized text. The reason for the first line is let's say we've already gotten results from compile, we don't want to got through the stress of api_get -> streams -> compile, yes they take lesser time than before doesn't mean they still won't take time loading, if for every time we click on a button to show all users or offline or online users and it loads all over again, the app is going to be very tiring, so what can we do? We can store it in a variable or local storage in your browser but this second method is flawed in so that if the status suddenly change, we can easily know by refreshing the page, the variable is refreshed but local storage stays the same, we can use local session then, it helps and works the same as variable. But we're just building a simple app here, no need to overkill it, so we went with variable. Remember the alli array defined earlier? So the first line checks if we already filled the alli array already, if not then run this.compile and await the result, then assign it to all. The last three lines is to change classes for specific styling used.

Next is the offline function and is similar to the all function.

//a function that compiles the offline users with use as above
  offline(){
    let off=alli
    document.getElementById('channel').innerHTML=''
    let of=off.filter((j)=>j.stream===null)
    let color='background-color:#FFCDD2'
    of.map((j)=>{
      document.getElementById('channel').innerHTML+='<div class="card" style='+color+'><img class="img" src='+j.logo+'><a href='+j.url+'><h3>'+j.display_name+'</h3></a><h5>offline</div><br>'
    })
    classer1='v'
    classer3='v active'
    classer2='v';
  }

Note the similarity except for change in classes and just using the alli array directly because the default result is always the all function, so by the time we're calling the offline function, the alli array would have been filled already.

So similarly, we have the online function, this returns the list of users that are online unlike the offline function that return users that are offline.

//this function does as above but for online users
  online(){
    let on=alli
    document.getElementById('channel').innerHTML=''
    let onn=on.filter((i)=>i.stream!==null)
    let color='background-color:#80CBC4'
    onn.map((j)=>{
      let text=j.game+': '+j.status
      document.getElementById('channel').innerHTML+='<div class="card" style='+ color +'><img class="img" src='+j.logo+'><a href='+j.url+'><h3>'+j.display_name+'</h3></a><h5>'+text+'</div><br>'
    })
    classer1='v'
    classer3='v'
    classer2='v1 active';
  }

And, we're almost done just the render remaining, which I will show below.

render() {
    this.all()
    return (
     <div>
        <div className='top'>
          <h1>Twitch Streamers </h1>
          <ul>
            <li className={classer1} onClick={this.all}><div id='all' className='cir'/>  <p className='i'> All</p></li>
            <li className={classer2} onClick={this.online}><div id='online' className='cir'/> <p className='ii'> Online</p></li>
            <li className={classer3} onClick={this.offline}><div id='offline' className='cir'/> <p className='iii'> Offline</p></li> 
          </ul> 
        </div>
        <div id='channel' />
     </div>
    );
  }

Then we render to the root div in the html and then style it.

ReactDOM.render(<Main />, document.getElementById('root'));

My html code looks like this,

<html>
    <head>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div id="root"></div>
        <script src="index.js"></script>
    </body>
</html>

We're done! You can style the app to your taste.

You can check the result here

Top comments (0)