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.
It took me some weeks to implement the SoundCloud Client FaveSound . Being both a passionate SoundCloud consumer and producer , it felt compelling for me to do my own SoundCloud client in React + Redux.
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 list of extensions which can be applied on top of the SoundCloud Client with React + Redux tutorial afterwards.
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.
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 JSX syntax takes getting used to. Basically we can use JavaScript in HTML. In our code snippet we map over a list of tracks and return a HTML node with track properties.
The console output gives the hint of a missing key property. React elements need that key property to uniquely identify themselves in a list of elements . Letâs fix this, save the file and see how hot reloading kicks in and refreshes our page!
Now itâs time to write our first real component. We can extract the rendered list of tracks in an own component, because the src/index.js should be only seen as entry point to the React application.
We import a Stream component which gets a list of tracks as props. Moreover we use that component as first parameter for ReactDOM.render . Now letâs implement the Stream component.
Our src folder is getting its first structure. We will organise our files by a technical separation - starting with a components folder, but later on adding more folders aside.
While itâs good to have a technical separation of concerns in an early project, it may not scale for larger applications. You might want to consider to organise your app by features with a growing code base .
Letâs give our recent created file some content.
src/components/Stream.js
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 .
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.
Letâs keep the state out of our component for the sake of simplicity.
Since we donât need internal component state nor lifecycle methods, we can refactor our ES6 class component to a stateless functional component .
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.
Itâs done. We have written our first React code!
A lot of things already happened during the last chapters. Letâs summarise these with some notes:
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.
I want to show you a simple setup to test your React components. I will do this by testing the Stream component, but later on I will not go any deeper into the topic of testing.
We will use mocha as test framework, chai as assertion library and jsdom to provide us with a pure JavaScript DOM implementation which runs in node.
Moreover we need a test setup file for some more configuration especially for our virtual DOM setup.
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.
Now we are set to write our first component test.
From components folder:
src/components/Stream.spec.js
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.
Moreover we can enhance our package.json scripts collection by a test:watch script.
By running the script we can see our tests executed every time we change something in our source code.
We wonât create anymore tests during this tutorial. As exercise feel free to add more tests during the next chapters!
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.
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.
Letâs get started with Redux by implementing our first roundtrip!
Letâs dispatch our first action and get some explanation afterwards.
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.
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.
When your project grows, there exist other folder/file structure patterns to organise your Redux code.
src/constants/actionTypes.js
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.
src/actions/track.js
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:
src/actions/index.js
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.
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.
Letâs create our first reducer.
src/reducers/track.js
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.
From reducers folder:
src/reducers/index.js
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.
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.
Letâs create the store file.
src/stores/configureStore.js
Redux provides us with a createStore function which takes the rootReducer and an initial state.
Letâs add a store middleware to even the way to a mature Redux application.
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.
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.
Letâs start our app again and see what happens.
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.
As I mentioned early there exist some libraries to wire Redux to other environments. Since we are using React, we want to connect Redux to our React components .
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.
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.
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.
As you can see the component itself doesnât change at all.
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.
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.
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.
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.
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.
src/components/Stream/index.js
src/components/Stream/presenter.js
src/components/Stream/spec.js
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.
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.
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.
From constants folder:
src/constants/auth.js
Now we can authenticate with SoundCloud.
The authentication process relies on a route called â/callbackâ in our app. Therefore we need to setup React Router to provide our app with some simple routing.
You have to add the following line to your web pack configuration.
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.
At the end there are two new components: App as component wrapper and Callback for the authentication. Letâs create the first one.
src/components/App/index.js
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.
Letâs create our Callback component.
src/components/Calback/index.js
Thatâs the default implementation to create the callback for the SoundCloud API. We do not need to touch this file anymore in the future.
The last step for the Router setup is to provide our store with the route state when we navigate from page to page.
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.
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.
We are able to connect to the SoundCloud API, login with our credentials and see our account details in the console output.
Nobody is triggering that action though, so letâs do that for the sake of simplicity in our Stream component.
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.
Now letâs use this new available action in our presenter component.
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.
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.
Building React Applications with Idiomatic Redux by egghead.io and Dan Abramov shows you how to implement your own thunk middleware.
Some side-effect middleware in Redux:
Letâs add thunk as middleware to our store.
src/stores/configurationStore.js
Now we have everything in place to save our user object to the store. Therefore we need to create a new set of action type, action creator and reducer.
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.
Letâs add the new reducer.
src/reducers/auth.js
The reducer respects the new action type and returns a newState with our user in place.
Now we want to see visually in our DOM whether the login was successful. Therefor we can exchange the Login button once the login itself was successful.
In our container component we map our new state, the current user, to the presenter component.
The presenter component decides whether it has to show the username or the Login button. When we start our app again and login, we should the displayed username instead of a button.
Now we are authenticated with the SoundCloud server. Letâs get real and fetch some real tracks and replace the hardcoded tracks.
We only removed the hardcoded tracks in here. Moreover we donât dispatch anymore an action to set some initial state.
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.
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.
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!
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.
In our container Stream component we can map that action to the presenter component.
Now we will have to implement the non existent playTrack action creator.
Donât forget to export a new action type as constant.
In our reducer we make place for another initial state. In the beginning there will be no active track set, but when we trigger to play a track, the track should be set as activeTrack.
Additionally we want to show the currently played track, therefore we need to map the activeTrack in our Stream container component.
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!
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.
Letâs start our app again. We login, we see our tracks as a list, we are able to hit the play button, we listen to music! I hope it works for you!
Add one of the following tutorials on top of your current SoundCloud project:
In case you want to know which versions npm installed during that tutorial, here a list of all npm packages in my package.json.
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.
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!