Never miss an article about web development, JavaScript and self-growth.

Take Part

The SoundCloud Client in React + Redux

In the beginning of 2016 it was time for me to deep dive into the ReactJs world. I read tons of articles about React and its environment, especially Redux, so far. Several of my colleagues used it in side projects and on a theoretical level I could participate in the discussions.

In my company we relied heavily on Angular 1 at this point. Since we are using it in a quite large code base, we know a lot about its flaws. Back in 2015 we already adopted our own flux architecture in the Angular world with the usage of stores and an unidirectional data flow. We were highly aware of the change coming with the React environment.

Again in the early days of 2016 I wanted to see this hyped paradigm shift in its natural environment (React and its flux successor Redux) with a hands on side project.

Professionally I grew with the code base, but also got an entry point into the open source community by providing a larger code base example for beginners in the React + Redux world. Since I made this great experience, I wanted to give the community this hands on tutorial, which will guide people to get started in React + Redux with a compelling real world application - a SoundCloud client.

At the end of this tutorial you can expect to have a running React + Redux app, which consumes the SoundCloud API (What’s an API?). You will be able to login with your SoundCloud account, list your latest tracks and listen to them within the browser. Additionally you will learn a lot about tooling with Webpack and Babel.

In the future I am going to write some smaller tutorials based on this one. They will simply build on top of this project and help you to get started in various topics. So keep an eye on this tutorial, follow me on Twitter or GitHub or simply star the repository to get updates.

A project from scratch

I must say I learned a lot from implementing a project from scratch. It makes totally sense to set up your side project from zero to one without having a boilerplate project. You will learn tons of stuff not only about React + Redux, but also about JavaScript in general and its environment. This tutorial will be learning by doing by understanding each step, like it was for me when I did this whole project, with some helpful explanations. After you have finished this, you should be able to set up your own React + Redux side project to provide another real world project for the community.

The whole tutorial contains a lot of information. I wouldn’t suggest to do everything at once when you are still learning React + Redux. Make some breaks between the chapters. Once you build your first React component, don’t continue with Redux immediately. Experiment a bit with the code, do some internal state management with React, before you use Redux for state management. Take your time.

Additionally I can recommend to read The Road to learn React before you dive into Redux. It teaches React by building a Hacker News App without configuration, tooling and Redux. If you are new to React, do yourself a favour and learn React first.

The Road to learn React

Build a Hacker News App along the way. No setup configuration. No tooling. No Redux. Plain React in 200+ pages of learning material. Pay what you want like 50.000+ readers.

Let’s get started

Before you can write your first React component, you have to install Webpack and Babel. I extracted the React setup into an own article to make it reusable and maintainable for the future. You can follow the instructions in the article to setup your project. After that you can come back to this tutorial and continue here to write your first React component.

Is your project set up? Then let’s render some data. It makes sense to render a list of tracks, since we are writing a SoundCloud application.

The Stream component is a React ES6 class component. The render shorthand function returns the element. Additionally we retrieve the props from this by using ES6 destructuring and providing a default empty list.

React ES6 class components provide a slim API. These lifecycle methods can be used to hook into the component lifecycle. For instance you can do things before a component gets rendered with componentWillMount() or when it updated with componentDidUpdate(). You can read about all component lifecycle methods.

classStreamextendsReact.Component{render(){...}componentWillMount(){// do things}componentDidUpdate(){// do things}}

ES6 class components can have internal component state. Imagine you could like a track. You would have to save the state whether a track is liked or not liked. I will demonstrate how you can achieve it.

You would need a contructor to setup the initial internal component state. Afterwards you can use setState() to modify the state and this.state to get the state. We modify the state in the onClick handler and get the state to show a button label.

It’s called stateless functional component, because it only gets an input and generates an output. There are no side effects happening (functional) and our component doesn’t know internal state at all (stateless). It’s only a function which gets a state and returns a view: (State) => View.

You can use ES6 class components whenever you need component lifecycle methods or internal component state. If that’s not the case, use functional stateless components.

A lot of things already happened during the last chapters. Let’s summarise these with some notes:

we use webpack + webpack-dev-server for bundling, building and serving our app

we use Babel

to write in ES6 syntax

to have .js rather than .jsx files

the src/index.js file is used by Webpack as entry point to bundle all of its used imports in one file named bundle.js

bundle.js is used in dist/index.html

dist/index.html provides us an identifier as entry point for our React root component

we set up our first React hook via the id attribute in src/index.js

we implemented our first component as stateless functional component src/components/Stream.js

You may want to experiment a bit more with React before you dive into Redux. Build some more ES6 class and functional stateless components. Additionally use lifecycle methods and internal component state to get used to it. Only then you will see the benefits of using Redux for state management.

Essentially we are exposing globally a jsdom generated document and window object, which can be used by React during tests. Additionally we need to expose all properties from the window object that our running tests later on can use them. Last but not least we are giving global access to the objects React and expect. It helps us that we don’t have to import each of them in our tests.

In package.json we will have to add a new script to run our tests which respects Babel, uses mocha as test framework, uses our previously written test/setup.js file and traverses through all of our files within the src folder with a spec.js suffix.

Additionally there are some more neat libraries to help us with React component tests. Enzyme by Airbnb is a library to test React components. It relies on react-addons-test-utils and react-dom (the latter we already installed via npm).

Jest can be used alone or in combination with enzyme to test React components. It’s the official library by Facebook.

From root folder:

npminstall--save-devreact-addons-test-utilsenzyme

Now we are set to write our first component test.

From components folder:

touchStream.spec.js

src/components/Stream.spec.js

importStreamfrom'./Stream';import{shallow}from'enzyme';describe('Stream',()=>{constprops={tracks:[{title:'x'},{title:'y'}],};it('shows two elements',()=>{constelement=shallow(<Stream{...props}/>);expect(element.find('.track')).to.have.length(2);});});

Here we are serving our Stream component with an array of two tracks. As we know both of these tracks should get rendered. The expect assertion checks whether we are rendering two DOM elements with the class track. When we run our tests, they should pass.

From root folder:

npmtest

Moreover we can enhance our package.json scripts collection by a test:watch script.

We won’t create anymore tests during this tutorial. As exercise feel free to add more tests during the next chapters!

Redux

Redux describes itself as predictable state container for JS apps. Most of the time you will see Redux coupled with React used in client side applications. But it’s far more than that. Like JavaScript itself is spreading on server side applications or IoT applications, Redux can be used everywhere to have a predictable state container. You will see that Redux is not strictly coupled to React, because it has its own module, while you can install another module to connect it to the React world. There exist modules to connect Redux to other frameworks as well. Moreover the ecosystem around Redux itself is huge. Once you dive into it, you can learn tons of new stuff. Most of the time it is not only just another library: You have to look behind the facade to grasp which problem it will solve for you. Only then you should use it! When you don’t run into that problem, don’t use it. But be curious what is out there and how people get creative in that ecosystem!

At this point I want to show some respect to Dan Abramov, the inventor of Redux, who is not only providing us with a simple yet mature library to control our state, but also showing a huge contribution in the open source community on a daily basis. Watch his talk from React Europe 2016 where he speaks about the journey of Redux and what made Redux successful.

Redux Roundtrip

I call it the Redux Roundtrip, because it encourages you to use a unidirectional data flow. The Redux Roundtrip evolved from the flux architecture. Basically you trigger an action in a component, it could be a button, someone listens to that action, uses the payload of that action, and generates a new global state object which gets provided to all components. The components can update and the roundtrip is finished.

As you can see we initialise a store object with some imported function we didn’t define yet. The store is a singleton Redux object and holds our global state object. Moreover it is possible to use a lightweight store API to dispatch an action, get the state of the store or subscribe to the store when updates occur.

In this case we are dispatching our first action with a payload of our hardcoded tracks. Since we want to wire our Stream component directly to the store later on, we don’t need to pass anymore the tracks as properties to our Stream component.

Where will we continue? Either we can define our configureStore function which generates the store object or we can have a look at our first dispatched action. We will continue with the latter by explaining actions and action creators, go over to reducers which will deal with the global state object and at the end set up our store which holds the global state object. After that our component can subscribe to the store to get updates or use the stores interface to dispatch new actions to modify the global state.

Constant Action Types

It is good to have a constants folder in general, but in early Redux projects you will often end up with some constants to identify your actions. These constants get shared by actions and reducers. In general it is a good approach to have all your action constants, which describe the change of your global state, at one place.

Action Creators

Now we get to the action creators. They return an object with a type and a payload. The type is an action constant like the one we defined in our previous created action types. The payload can be anything which will be used to change the global state.

Our first action creator takes as input some tracks which we want to set to our global state. It returns an object with an action type and a payload.

To keep our folder structure tidy, we need to setup an entry point to our action creators via an index.js file.

From actions folder:

touchindex.js

src/actions/index.js

import{setTracks}from'./track';export{setTracks};

In that file we can bundle all of our action creators to export them as public interface to the rest of the app. Whenever we need to access some action creator from somewhere else, we have a clearly defined interface for that, without reaching into every action creator file itself. We will do the same later on for our reducers.

Reducers

After we dispatched our first action and implemented our first action creator, someone must be aware of that action type to access the global state. These functions are called reducers, because they take an action with its type and payload and reduce it to a new state (previousState, action) => newState. Important: Rather than modifying the previousState, we return a new object newState - the state is immutable.

The state in Redux must be treated as immutable state. You will never modify the previous state and you will always return a new state object. You want to keep your data structure immutable to avoid any side effects in your application.

As you can see we export an anonymous function, the reducer, as an interface to our existing app. The reducer gets a state and action like explained previously. Additionally you can define a default parameter as a function input. In this case we want to have an empty array as initial state.

The initial state is the place where you normally would put something like our hardcoded tracks from the beginning, rater than dispatching an action (because they are hardcoded). But later on, we want to replace these tracks with tracks we fetched from the SoundCloud API, and thus we have to set these tracks as state via an action.

The reducer itself has a switch case to differ between action types. Now we have only one action type, but this will grow by adding more action types in an evolving application.

After all we use the ES6 spread operator to put our previous state plus the action payload, in that case the tracks, in our returned new state. We are using the spread operator to keep our object immutable. I can recommend libraries like Immutable.js in the beginning to enforce the usage of immutable data structures, but for the sake of simplicity I will go on with pure ES6 syntax.

Again to keep our folder interfaces tidy, we create an entry point to our reducers.

Saving us some refactoring, I already use a helper function combineReducers here. Normally you would start to export one plain reducer. That reducer would return the whole state. When you use combineReducers, you are able to have multiple reducers, where each reducer only returns a substate. Without combineReducers you would access your tracks in the global state with state.tracks. But with combineReducers you get these intermediate layer to get to the subset of states produced by multiple reducers. In that case state.track.tracks where track is our substate to handle all track states in the future.

Store with Global State

Now we dispatched our first action, implemented a pair of action type and action creator, and generated a new state via a reducer. What is missing is our store, which we already created from some not yet implemented function in our src/index.js.

Remember when we dispatched our first action via the store interface store.dispatch(actionCreator(payload))? The store is aware of the state and thus it is aware of our reducers with their state manipulations.

The Redux store is aware of a middleware, which can be used to do something between dispatching an action and the moment it reaches the reducer. There is already a lot of middleware for Redux out there. Let’s use the logger middleware for the beginning.

npminstall--saveredux-logger

The logger middleware shows us console output for each action: the previous state, the action itself and the next state. It helps us to keep track of our state changes in our application.

In the browser we don’t see the tracks from our global store, because we don’t pass any global state to our Stream component yet. But we can see in the console output our first action which gets dispatched.

Let’s connect our Stream component to the Redux store to close the Redux Roundtrip.

Do you remember when I told you about the lightweight Redux store API? We will never have the pleasure to enjoy the store.subscribe functionality to listen to store updates. With react-redux we are skipping that step and let this library take care of connecting our components to the store to listen to updates.

Essentially we need two steps to wire the Redux store to our components. Let’s begin with the first one.

Provider

The Provider from react-redux helps us to make the store and its functionalities available in all child components. The only thing we have to do is to initiate our store and wrap our child components within the Provider component. At the end the Provider component uses the store as property.

Now we made the Redux store available to all child components, in that case the Stream component.

Connect

The connect functionality from react-redux helps us to wire React components, which are embedded in the Provider helper component, to our Redux store. We can extend our Stream component as follows to get the required state from the Redux store.

Remember when we passed the hardcoded tracks directly to the Stream component? Now we set these tracks via the Redux Roundtrip in our global state and want to retrieve a part of this state in the Stream component.

Basically we are using the returned function of connect to take our Stream component as argument to return a higher order component. The higher order component is able to access the Redux store while the Stream component itself is only presenting our data.

Additionally the connect function takes as first argument a mapStateToProps function which returns an object. The object is a substate of our global state. In mapStateToProps we are only exposing the substate of the global state which is required by the component.

Moreover it is worth to mention that we could still access properties given from parent components via <Stream something={thing} /> via the mapStateToProps function. The functions gives us as second argument these properties, which we could pass with out substate to the Stream component itself.

functionmapStateToProps(state,props){…}

Now start your app and you should see this time the rendered list of tracks in your browser. We already saw these tracks in a previous step, but this time we retrieve them from our Redux store.

The test should break right now, but we will fix that in the next step.

Container and Presenter Component

Our Stream component has two responsibilities now. First it connects some state to our component and second it renders some DOM. We could split both into container and presenter component, where the container component is responsible to connect the component to the Redux world and the presenter component only renders some DOM.

Let’s refactor!

First we need to organise our folder. Since we will not only end up with one file for the Stream component, we need to set up a dedicated Stream folder with all its files.

From components folder:

mkdirStreamcdStreamtouchindex.jstouchpresenter.jstouchspec.js

The Stream folder consists of an index.js file (container), presenter.js file (presenter) and spec.js file (test). Later on we could have style.css/less/scss, story.js etc. files in that folder as well.

Let’s refactor by each file. While every line of code is new in these files, I highlighted the important new parts coming with that refactoring. Most of the old code gets only separated in the new files.

importStreamfrom'./presenter';import{shallow}from'enzyme';describe('Stream',()=>{constprops={tracks:[{title:'x'},{title:'y'}],};it('shows two elements',()=>{constelement=shallow(<Stream{...props}/>);expect(element.find('.track')).to.have.length(2);});});

Now you can delete the old files Stream.js and Stream.spec.js, because they got refactored into the new Stream folder.

When you start your app, you should still see the list of tracks rendered. Moreover the test should be fixed again.

In the last steps we finished the Redux Roundtrip and connected our components to the Redux environment. Now let’s dive into our real world application - the SoundCloud client.

SoundCloud App

There is nothing better than having an app with some real data showing up. Rather than having some hardcoded data to display, it is an awesome feeling to fetch some data from a well known service like SoundCloud.

In the chapter of this tutorial we will implement our SoundCloud client, which means that we login as SoundCloud user and show our latest track stream. Moreover we will be able to hit the play button for these tracks.

Registration

Before you can create a SoundCloud client, you need to have an account and register a new app. Visit Developers SoundCloud and click the “Register a new app” link. Give your app a name and “Register” it.

In the last registration step you give your app a “Redirect URI” to fulfil the registration later in the app via a login popup. Since we are developing locally, we will set this Redirect URI to “http://localhost:8080/callback”.

The port should be 8080 by default, but consider to change this according to your setup.

The previous step gives us two constants which we have to use in our app: Client ID and Redirect URI. We need both to setup our authentication process. Let’s transfer these constants into a file.

The historyApiFallback allows our app to do routing purely on the client side. Usually a route change would result into a server request to fetch new resources.

Let’s provide our app with two routes: one for our app, another one for the callback and authentication handling. Therefore we use some helper components provided by react-router. In general we have to specify path and component pairs. Therefore we define to see the Stream component on the root path “/” and the Callback component on “/callback” (that’s where the authentication happens). Additionally we can specify a wrapper component like App. We will see during its implementation, why it is good to have a wrapper component like App. Moreover we use react-router-redux to synchronise the browser history with the store. This would help us to react to route changes.

App does not much here but passing all children. We will not use this component in this tutorial anymore, but in future implementations you could use this component to have static Header, Footer, Playlist or Player components while the children are changing.

Moreover we sync our store with the browser history, so that we can listen later on to events based on our current route. We will not use that in this tutorial, but it can help you to fetch data on route changes for instance. Additionally properties like browser path or query params in the URL can be accessed in the store now.

Authentication

Let’s authenticate with SoundCloud! We need to setup a new action to trigger that an event to authenticate. Let’s expose the auth function already and add the required action file afterwards.

In our container component we did only map some state to our presenter component. Now it comes to a second function we can pass to the connect function: mapDispatchToProps. This function helps us to pass actions to our presenter component. Within the mapDispatchToProps we return an object with functions, in this case one function named onAuth, and use our previously created action auth within that. Moreover we need to bind our action creator with the dispatch function.

We simply put in a button and pass the onAuth function as onClick handler. After we start our app again, we should see the current user in the console output after we clicked the Login button. Additionally we will still see some error message, because our action goes nowhere, since we didn’t supply a according reducer for it.

We might need to install a polyfill for fetch, because some browser do not support the fetch API yet.

Redux Thunk

We can see our current user object in the console output, but we don’t store it yet! Moreover we are using our first asynchronous action, because we have to wait for the SoundCloud server to respond our request. The Redux environment provides several middleware to deal with asynchronous actions (see list below). One of them is redux-thunk. The thunk middleware returns you a function instead of an action. Since we deal with an asynchronous call, we can delay the dispatch function with the middleware. Moreover the inner function gives us access to the store functions dispatch and getState.

Instead of doing the console output when we retrieved the user object, we simply call our action creator. Moreover we can see that the thunk middleware requires us to return a function instead of an object. The function gives us access to the dispatch functionality of the store.

After the authentication we simply dispatch a new asynchronous action to fetch track data from the SoundCloud API. Since we already had an action creator to set tracks in our state, wen can reuse this.

The returned data hasn’t only the list of tracks, it has some more meta data which could be used to fetch more paginated data afterwards. You would have to save the next_href property of data to do that.

The data structure of the SoundCloud tracks looks a bit different than our hardcoded tracks before. We need to change that in our Stream presenter component.

Moreover we need to adjust our test that it respects the new track data structure.

src/components/Stream/spec.js

importStreamfrom'./presenter';import{shallow}from'enzyme';describe('Stream',()=>{constprops={tracks:[{origin:{title:'x'}},{origin:{title:'y'}}],};it('shows two elements',()=>{constelement=shallow(<Stream{...props}/>);expect(element.find('.track')).to.have.length(2);});});

When you start your app now, you should see some tracks from your personal stream listed after the login.

Even if you created a new SoundCloud account, I hope you have a stream displayed though. If you get some empty stream data, you have to use SoundCloud directly to generate some e.g. via following some people.

From root folder:

npmstart

SoundCloud Player

How would it be to have your own audio player within the browser? Therefor the last step in this tutorial is to make the tracks playable!

Another Redux Roundtrip

You should be already familiar with the procedure of creating action, action creator and reducer. Moreover you have to trigger that from within a component. Let’s start by providing our Stream component some yet not existing onPlay functionality. Moreover we will display a Play button next to each track which triggers that functionality.

By starting our app, we should be able to login, to see our tracks and to play a track. The redux-logger should show some console output that we have set an activeTrack. But there is no music yet! Let’s implement that!

Listen to the music!

In our last step we already handed the activeTrack to our presenter Stream component. Let’s see what we can do about that.

We need the CLIENT_ID to authenticate the audio player with the SoundCloud API in order to stream a track via its stream_url. In React 15 you can return null, when there is no activeTrack. In older versions you had to return <noscript />.

When we start our app and try to play a track, the console output says that we cannot define refs on stateless functional components. But we need that reference on the audio element to be able to use its audio API. Let’s transform the Stream presenter component to a stateful component. We will see how it gives us control over the audio element.

After all you should avoid to have stateful components and try to stick to functional stateless components. In this case we have no other choice.

Final Thoughts

Hopefully you enjoyed this tutorial and learned a lot like I did. I didn’t plan to write so much in the first place, but I hope at the end it reaches enough people to encourage them to learn something new or simply to setup their own project.

I am open for feedback or bug reports on this tutorial. Please comment directly or reach out on Twitter.

Moreover have a look again at favesound-redux. Feel free to try it, to contribute, to raise issues when you find bugs or to use it as blueprint for your own application.

In conclusion keep an eye on that tutorial. I will add more smaller content in the future. Have a look at the next chapter for more information.

Contribute

I already mentioned it, but feel free to contribute to favesound-redux. Get in contact with me, there is plenty of stuff to do and it gives you a start into the open source community.

Moreover I want to extend this tutorial with smaller tutorials on top. Like I explained in Tutorial Extensions you can contribute in this repository and add your own folder in there which builds on top of the init folder. In your own folder you can address a new topic. There is a lot of potential!