Redux From Scratch (Chapter 4 | Async Practice With Twitch API)

Buy the Official Ebook

If you would like to support the author and receive a PDF, EPUB, and/or MOBI copy of the book, please purchase the official ebook.

Prerequisites

Chapter 1 | Core Concepts
Chapter 2 | Practicing the Basics
Chapter 3 | Implementing With React

Scope of This Chapter

In the previous chapter, we were able to create our first React & Redux application. However, all of our data was manually defined. In a real-world scenario, we would be working with an API to fetch data to populate our user interface. Working with an API also introduces extra things to consider. In this chapter, we will practice working with the Twitch API as we unpack how to create a Redux application that is asynchronous.

Project Setup

We are going to be using the Twitch API to retrieve 25 featured streams. We will render the preview images of each stream which will take a user to the stream on a click.

The initial project setup will be the same process as the previous chapter. To save time, go ahead and download the template of this project via GitHub.

There are some dummy.txt files which you can remove.

Actions for Asynchronous Application

When we are working with an API, our application becomes asynchronous. Meaning, our user interface will have to wait for data to be fetched before it can be rendered as desired. In our case, we have to wait for a response from our request to fetch streaming Twitch videos before we can fill in our user interface.

Actions

Because of this, we have a few more actions to create for our application. We need actions that will let a reducer know where we are it in the process of fetching data. That way, we can render something while the data is being fetched, when the data has been loaded, and if there is an error fetching the data.

The first action will let the reducer that the API request began.

When the reducer receives this action, we can render a loader to the user interface. Here’s an example loader I created:

The second action will let the reducer know the API request received a successful response.

At this point, we will toggle off the loader and render 25 Twitch streams to the user interface in its place.

The third action will let the reducer know that the API request failed.

If this happens, we will display an error message to the user interface.

There will be a property in our state called status that we can initialize and update with our actions like so:

status: "" //initial
status: "loading" //API request began
status: "success" //API request successful
status: "error" //API request failed

With this information available to our React container components, we will use conditional rendering to determine what is being rendered to the user interface.

Here’s some example action objects:

{ type: 'FETCH_REQUEST', status: "loading" }
{ type: 'FETCH_FAILURE', status: "error", error: 'Request failed.' }
{ type: 'FETCH_SUCCESS', status: "success", response: { ... } }

Knowing all of this, let’s start to write the asynchronous actions with action creators for our project.

Action Creators

Let’s start with a new file within our actions folder called FetchRequest.js.

We can add the following:

//define action within an action creator
function FetchRequest() {
const FETCH_REQUEST = 'FETCH_REQUEST'
return {
type: FETCH_REQUEST,
status: "loading"
}
}
export default FetchRequest

This code is very straightforward. We just need to let the reducer know that the fetch request began so our UI can render a loader for the “loading” phase.

Next, we can create another file within our actions folder called FetchSuccess.js and add the following code:

//define action within an action creator
function FetchSuccess(streams) {
const FETCH_Success = 'FETCH_Success'
return {
type: FETCH_SUCCESS,
status: "success",
streams
}
}
export default FetchSuccess

This code has a tad more going on. In addition to our UI knowing that the request was a success, it also needs the retrieved videos of the streams. This will be passed in as a parameter and included in the action definition.

The final asynchronous action creator will be FetchFailure.js:

//define action within an action creator
function FetchFailure(error) {
const FETCH_FAILURE = 'FETCH_FAILURE'
return {
type: FETCH_FAILURE,
status: "error",
error
}
}
export default FetchFailure

In this code, we define a status of “error” and an error message that is passed in as a parameter.

Just like that, our actions and action creators are complete! Not too bad!

Defining Our Reducer

Now that we have completed our asynchronous actions, let’s go ahead and begin defining our reducer. The reducer will set the initial state and have the logic to update the state on the incoming asynchronous actions.

Create a new file within the reducers folder called TwitchApp.js.

In this file, let’s go ahead and create the initial state object:

//define the initial state
const initialState = {
status: "",
streams: [],
error: ""
}

Here, we a property to hold the status which will be updated by the actions we just defined. We also have a property called streams which will hold an array of Twitch streams retrieved by our API request. Lastly, we have an error property to store an error message in case of a failure.

Let’s then add the shell of our reducer and the handling of the FETCH_REQUEST action:

//define a reducer with an initalized state and logic to handle action
function TwitchApp(state = initialState, action) {
switch(action.type) {
case 'FETCH_REQUEST':
const requested = Object.assign({}, state, {
status: action.status
})
return requested
default:
return state
}
}
export default TwitchApp

On the FETCH_REQUEST action, we copy the current state with an updated status which will be “loading”.

On the FETCH_SUCCESS action, we copy the current state with an updated status (which will be “success”) and the streams:

switch(action.type) {
case 'FETCH_REQUEST':
const requested = Object.assign({}, state, {
status: action.status
})
return requested
case 'FETCH_SUCCESS':
const successful = Object.assign({}, state, {
status: action.status,
streams: action.streams
})
return successful
default:
return state
}

Finally, we add the logic for our FETCH_FAILURE action which includes a status and error update:

switch(action.type) {
case 'FETCH_REQUEST':
const requested = Object.assign({}, state, {
status: action.status
})
return requested
case 'FETCH_SUCCESS':
const successful = Object.assign({}, state, {
status: action.status,
streams: action.streams
})
return successful
case 'FETCH_FAILURE':
const failed = Object.assign({}, state, {
status: action.status,
error: action.error
})
return failed
default:
return state
}

For now, our reducer is complete!

Creating Our Loader

Setting Up Our Container Component

So far, we have done the setup for our initial Redux code. Now, it’s time to begin completing the React components. In this section, we want our React container component to make the API request and dispatch a FETCH_REQUEST action.

First off, we need to initialize the store. Open index.js.

Let’s begin by importing createStore from the Redux library:

import { createStore } from 'redux';

We also need to import our reducer:

import TwitchApp from './reducers/TwitchApp';

Then, we can add the initialization of the store between the class and ReactDOM.render(...) :

//initialize store
let store = createStore(TwitchApp)

Next, we need to make the store accessible to the container component so the state can be retrieved.

Import Provider predefined component from React-Redux library:

import { Provider } from 'react-redux';

Then, wrap the Provider component around the nested <App/> component in the ReactDOM render with the state being passed down as a prop:

ReactDOM.render(
<Provider store = { store }>
<App/>
</Provider>,
document.getElementById('app')
)

Finally, we can import the container component that we will make next called Streams and nest it with the store prop being passed down:

import Streams from './components/containers/Streams';
//top level of React component hierarchy
class App extends React.Component {
render() {
return (
<div className="app">
<Streams store={store} />
</div>
)
}
}

Cool! Let’s move onto our container component which will be created in a file called Streams.js. Make sure to place this file in the containers folder.

Let’s add a bit of the code:

import React from 'react';
import { getState } from 'redux';
import FetchRequest from '../../actions/FetchRequest';
import FetchSuccess from '../../actions/FetchSuccess';
import FetchFailure from '../../actions/FetchFailure';
//Provider/Container React Component
class Streams extends React.Component {
render() {
const stateProps = this.props.store.getState();
return (
<div>
      </div>
)
}
}
export default Streams

In the code above, we are importing getState so we can store the state into a variable which we have done in the following line:

const stateProps = this.props.store.getState();

We have also already imported the action creators.

In a little bit, we can write the code to make the API request and dispatch an action at this time. Before that, let’s start the conditional rendering code so we can render our loader if the status within the state is set to “loading”.

First, we can store the status into a variable just above the return:

const status = stateProps.status;

Adding Conditional Rendering

Create a new file in the presentationals folder called Loader.js.

In this file, we will just focus on presenting a loader. For this loader, I am going to be using this pure CSS crystal loader that I made on Codepen:

I will simply be providing the code, but if you are curious on how to make this, I have a video tutorial on YouTube that you can watch.

import React from 'react';
//Presentational React Component
class Loader extends React.Component {
render() {
return (
<div className="loader">
<div className="box">
<div className="line-1" />
<div className="line-2">
<div className="triangle">
<div className="shine" />
</div>
</div>
<div className="line-3">
<div className="triangle-sm">
<div className="shine" />
</div>
</div>
<div className="line-4">
<div className="triangle-sm">
<div className="shine" />
</div>
</div>
</div>
</div>
)
}
}
export default Loader

Also, here’s the updated main.css file via GitHub Gist.

Next, open Streams.js and let’s import this new presentational component:

import Loader from '../presentationals/Loader';

We then can add some conditional rendering that will read as: “If status is equal to loading, then render a loader. Else, render an empty div.”

return (
<div>
{status === "loading" ? (
<Loader />
) : (
<div></div>
)
}
</div>
)

We should currently see nothing in the local host because the status is not loading by default. For now, we can make sure our conditional rendering is working by changing the status variable’s value to “loading”:

const status = "loading";

We should now see the following loader working properly:

Neat!

Let’s finish this section by making the API request and having the state change via a dispatched action so our loader renders when the status property is set to “loading”.

Creating an API Key

First off, create a Twitch account if you have not already.

Then, we can create a Twitch API key by visiting the connections page.

Under Other Connections, you can click to register an app and retrieve the API key.

When registering an app, make sure to specify the redirect URI as http://localhost:

Hit Register, copy the Client ID that is generated, and paste it into an empty file in your code editor.

We will be able to use this client ID in order to retrieve the data we need.

Installing Axios

To make API requests, we can use Axios.

We can install it by running the following in command line:

npm install axios --save

Before we move along, let’s import this into Streams.js where we will be making the API call:

import axios from 'axios';

API Request and Dispatching Our Action

We want to retrieve streaming videos from Twitch that can be played. We want to display the video cover image (previews) of 25 streams that link to the live stream.

We will retrieve the data of the streams from our Streams container component.

We want to retrieve these streaming videos before our app component mounts so we add the following in Streams.js:

componentWillMount() {
axios.get('')
.then(response => {
      })
.catch(e => {

});
}

The code above is the shell of an axios API request. It can be read as: “Get the data we need from this URL, then let us do something with the response. If you run into an error, give us the error code so we can do something with it.”

Within axios.get('') , we can put the URL that will return featured Twitch streams. The URL can be found on the official Twitch API documentation:

By default, it will give us 25 featured streams which are exactly what we want.

Knowing this, let’s insert the following URL:

componentWillMount() {
axios.get('https://api.twitch.tv/kraken/streams/featured')
.then(response => {
      })
.catch(e => {

});
}

Now, we need to a URL parameter that authenticates us to retrieve data. We add the client_id which we generated previously (it should have been placed in an empty file in your code editor):

//update with your client_ID
componentWillMount() {
axios.get('https://api.twitch.tv/kraken/streams/featured?client_id=skawlpb80ixx8e9cxafxepbn66xhe1')
.then(response => {
      })
.catch(e => {

});
}

Our code is now saying: “Hey, Twitch! We have an address for getting featured streams and some verification that you’re cool with me doing this. Once you give us what we need, then we’ll have to do something with what you gave us. If you catch an error, give the message to us so we can do something with it. ”

Let’s see if this is working by logging the response (we also can log the error if in case it doesn’t work):

componentWillMount() {
axios.get('https://api.twitch.tv/kraken/streams/featured?&client_id=skawlpb80ixx8e9cxafxepbn66xhe1')
.then(response => {
console.log(response);
})
.catch(e => {
console.log(error);
});
}

Refresh the local host and check the console. We should see the following data is returned:

In the retrieved data (Object), there is data (Object) for 25 featured streams within an array called featured.

Within one of the featured stream objects, there is another object called stream containing the information we need (cover/preview image URL and an ID):

This is a sneak peak at how we will render streams when we receive a successful response. However, let’s just worry about dispatching our FETCH_REQUEST action via the FetchRequest() action creator.

Once again, this will set the status equal to “loading” which should make our loader render.

First, let’s place the API request into a separate function that is called from the componentWillMount lifecycle hook:

componentWillMount () {
this.apiRequest();
}
apiRequest () {
axios.get('https://api.twitch.tv/kraken/streams/featured?&client_id=skawlpb80ixx8e9cxafxepbn66xhe1')
.then(response => {
console.log(response);
})
.catch(e => {
console.log(error);
});
}

Next, let’s create a function that will dispatch the FETCH_REQUEST action and call it from the componentWillMount lifecycle hook:

componentWillMount () {
this.apiRequest();
this.dispatchFetchRequest();
}
//apiRequest() here
dispatchFetchRequest () {
this.props.store.dispatch(FetechRequest());
}

Finally, we add the subscription and a forced update of our component as the callback:

componentWillMount () {
this.props.store.subscribe(this.forceUpdate.bind(this));
this.apiRequest();
this.dispatchFetchRequest();
}

Actually…one more thing. The update of the status property in our state via this dispatched action will happen so quick that it will be hard to notice if it’s even working. Therefore, let’s pause the dispatched action by 5 seconds:

componentWillMount () {
this.props.store.subscribe(this.forceUpdate.bind(this));
this.apiRequest();
setTimeout( () => {
this.dispatchFetchRequest();
}, 5000)
}

Let’s check the local host to make sure this is working:

Woohoo! One action down, two more to go!

Make sure to remove the delay we added so we just have this:

componentWillMount () {
this.props.store.subscribe(this.forceUpdate.bind(this));
this.apiRequest();
this.dispatchFetchRequest();
}

Rendering Streams

Dispatching Our Action

The next step is to dispatch our FETCH_SUCCESS action via the FetchSuccess() action creator.

We need this to occur when we receive a response. Therefore, we can do:

apiRequest () {
axios.get('https://api.twitch.tv/kraken/streams/featured?&client_id=skawlpb80ixx8e9cxafxepbn66xhe1')
.then(response => {
this.dispatchFetchSuccess()
console.log(response);
})
.catch(e => {
console.log(error);
});
}
//dispatchFetchRequest here
dispatchFetchSuccess () {
this.props.store.dispatch(FetchSuccess());
}

If we take a look at FetchSuccess.js, we also need to pass in the streams to this action creator:

//define action within an action creator
function FetchSuccess(streams) {
const FETCH_SUCCESS = 'FETCH_SUCCESS'
return {
type: FETCH_SUCCESS,
status: "success",
streams
}
}
export default FetchSuccess

Therefore, we need to get the streams into an array called streams from the response (path shown below), pass the streams array to dispatchFetchSuccess() like so this.dispatchFetchSuccess(streams) .

First, let’s get the streams into an array called streams from the response and pass it to dispatchFetchSuccess:

apiRequest () {
axios.get('https://api.twitch.tv/kraken/streams/featured?&client_id=skawlpb80ixx8e9cxafxepbn66xhe1')
.then(response => {
const streams = response.data.featured.map(function(feat) {
return feat.stream;
});
this.dispatchFetchSuccess(streams);
})
.catch(e => {
console.log(error);
});
}

The code to store the streams shown above can be read as: “For each featured stream within the featured array, return the stream object of that featured stream.” This may look complex but it is quite simple if you know how to follow the path of the response:

After that, we can add streams parameter and pass the streams array into the action creator:

dispatchFetchSuccess (streams) {
this.props.store.dispatch(FetchSuccess(streams));
}

Rendering to Match the Update State

At this point, our state should now contain a status of “success” and an array full of stream objects on the FETCH_SUCCESSaction.

The next step is to update our conditional rendering for this update of the state.

Like we did in the previous chapter, we want to render multiple presentational components in a similar fashion:

const bookItems = stateProps.books.map((book) =>
<BookCard
key = { book }
stateProps = { stateProps }
dispatchAction = {this.dispatchAction.bind(this)}
/>
);
return (
<div className="books-container">
{bookItems}
</div>
)

Let’s start this step by creating a new file called StreamCard.js within the presentationals folder.

Then, let’s import this into Streams.js like so:

import StreamCard from  '../presentationals/StreamCard';

Above the return, we can store a StreamCard component for every stream into a variable called streamCardItems. We pass each component a key, preview image, and stream (channel) url:

const streamCardItems = stateProps.streams.map((stream) =>
<StreamCard
key = { stream._id }
streamCover = { stream.preview.medium }
streamLink = { stream.channel.url }
/>
);

Here’s where we got stream.preview.medium and stream.channel.url:

Then, we update our conditional rendering so it reads as: “If status is equal to loading, render the loader. Else if the status is equal to loading, render the stream cards. Else, render an empty div.”

return (
<div>
{status === "loading" ? (
<Loader />
) : (
status === "success" ? (
<div className="stream-cards">
{streamCardItems}
</div>
) : (
<div></div>
)
)
}
</div>
)

Note that className=”stream-cards” is for styling purposes.

The final step is to fill in our StreamCard.js file like so:

import React from 'react';
//Presentational React Component
class StreamCard extends React.Component {
render() {
return (
<div className="stream-cards">
<a href={this.props.streamLink}>
<img
className="stream-cover"
src={this.props.streamCover}
/>
</a>
</div>
)
}
}
export default StreamCard

In the code above, we use this.props.streamLink to bind the link of each stream card that is rendered. We also use this.props.streamCover to bind the source of each image in our stream card.

There are also some classes for styling purposes. Speaking of which, update main.css to the following code found in this GitHub Gist.

Go ahead and check the local host. We should now see the following:

Super cool! We can view images for all 25 streams and click to view it.

If we keep refreshing the page, we can see that our loader is working:

If a user had a slow connection, the loader would display until the request gets a response which greatly improves the user experience.

Rendering Our Error

The last piece of this application is to dispatch our FETCH_FAILURE action and render an alert with the error message.

We can add a new function that will dispatch FetchFailure() with an error:

dispatchFetchFailure (error) {
this.props.store.dispatch(FetchFailure(error));
}

This function will be called if the axios API request receives an error:

apiRequest () {
axios.get('https://api.twitch.tv/kraken/streams/featured?&client_id=skawlpb80ixx8e9cxafxepbn66xhe1')
.then(response => {
console.log(response);
const streams = response.data.featured.map(function(feat) {
return feat.stream;
});
this.dispatchFetchSuccess(streams);
})
.catch(e => {
this.dispatchFetchFailure(e);
});
}

Notice how we passed in the error to this.dispatchFetchFailure. The dispatched action for the failure will update the error property in our state with the received error message.

We can then insert a presentational component for our alert when the status isn’t “loading” or “success” and is “error”:

return (
<div>
{status === "loading" ? (
<Loader />
) : (
status === "success" ? (
<div className="stream-cards">
{streamCardItems}
</div>
) : (
status === "error" ? (
<div>
//insert alert presentational component here
</div>
) : (
<div></div>
)
)
)
}
</div>
)

Above the return, we can store the current error message into a variable:

const error = stateProps.error;

Create a new file called Alert.js within the presentationals folder and add the following code:

import React from 'react';
//Presentational React Component
class Alert extends React.Component {
componentDidMount () {
alert(this.props.error);
}
render() {
return (
<div>
</div>
)
}
}
export default Alert

We will display a simple alert the error message that has been passed down as a prop when the component mounts.

The final step is to import this Alert component and pass down the error prop.

Back in Streams.js, add the import:

import Alert from  '../presentationals/Alert';

Then, nest this component in the conditional rendering with our error variable being passed down as the error prop:

status === "error" ? (
<div>
<Alert error = { error } />
</div>
)

To test this out, we can change the API request url to something bogus:

axios.get('give me an error!')

Make sure to save the actual API request url somewhere.

If you check the local host, we should now see the alert message when loading the page:

Perfect!

Paste back in the original API request url:

axios.get('https://api.twitch.tv/kraken/streams/featured?&client_id=skawlpb80ixx8e9cxafxepbn66xhe1')

We have now completed our first asynchronous application using React and Redux!

Final Code

Available on GitHub.

Concluding Thoughts

We should now feel more comfortable using Redux in a real-world React application. There are definitely some improvements we could make, however, which we will discuss next.

Chapter 5

Chapter 5 is now available.

Buy the Official Ebook

If you would like to support the author and receive a PDF, EPUB, and/or MOBI copy of the book, please purchase the official ebook.


Cheers, 
Mike Mangialardi

Like what you read? Give Michael Mangialardi a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.