Last updated 06/01/17 to use Create React App, Redux Form v6, React-Router v4, and Firebase v3. Looking for the old version of the code? Find it here
Table of Contents:
Quick note: I created a repo on GitHub to show the steps of building this app, so I'll be linking to commits in that repo along the way. You can view the seven steps of the process in the GitHub commit history, but I'll also link each step alongside the article.
A lot of people are excited about React, Facebook's new JavaScript framework. Big companies, among them Netflix, Yahoo!, Atlassian, and Khan Academy, are making React a critical part of their stack, and it seems like more and more developers are singing its praises (or, alternatively, complaining about JavaScript fatigue).
React is different. Really different. Almost every single previous popular library has focused on connecting JavaScript to your HTML inside of your HTML — such as jQuery's referencing elements in your HTML by their id
selectors or Angular's ng-
syntax.
React has flipped this paradigm on its head. Instead of writing HTML files and then figuring out how to pull in your JavaScript, you instead write JavaScript files and render your HTML from inside.
Most JavaScript frameworks operate by manipulating the DOM directly, but this can be very slow. React creates a copy of the DOM — known as the virtual DOM — and manipulates that instead. It then compares the virtual DOM with the real DOM and swaps out any changed pieces in a more efficient manner than is possible when trying to update the real DOM directly.
React isn't a MVC framework like Angular or Ember — in fact, it is only the V, or the view layer, in MVC. It doesn't concern itself with modeling your data or routing, and there are no controllers to deal with. Instead, you use React to build interactive and reusable UI components.
React itself is reasonably well-documented and easy to pick up, once you can shift your thinking to align with its conventions. The problem is, if you want to build robust apps, the V in MVC probably isn't going to cut it, and you have to dive into the often-confusing ecosystem surrounding React.
In this series, we're going to walk through the stages of building a React application — an app that lets you search the Giphy API and displays results, similar to what Giphy has on its own website.
You can find the Github repository with step-by-step commits here.
IMPORTANT NOTE: While we try to keep this tutorial updated for the latest versions of all the packages, things can change fast in the JavaScript ecosystem. If you are getting unexpected errors, your best bet is to check the
package.json
file in our project repository and make sure you're using the same versions that we used in the tutorial.
You don't need to have any React experience to work through this series, but you should probably have at least an intermediate-level background in JavaScript to understand what's going on.
You'll also want to have Node.js/npm installed. I will be using version 6.5.0
for this project.
If you've never used Webpack before, you might also want to take a look at our Unpacking Webpack article before diving in. While we're going to use a tool that handles most of the configuration for us, you might find it helpful to understand what's going on under the hood.
Ready? Let's begin!
In the previous version of this tutorial from March 2016, we had several sections dedicated to setting up React, Webpack, and Babel (so that we could write our code in ES6). However, a few months after we published the series, Facebook released Create React App, a command line tool to build React apps with zero build configuration.
To create our project, simply head over the console and enter the following commands:
$ npm install -g create-react-app$ create-react-app react-gif-search
Out of the box, we now have access to the following tools:
If you run $ npm start
in your console, your browser should open automatically and navigate to http://localhost:3000
:
Before we get started, we're going to do a bit of cleanup on some of the boilerplate files generated by Create React App. Within the src/
directory, delete every file except index.js
. For that file, you can delete all the code so we have an entirely fresh start.
To understand React, you need to think in terms of self-contained components. A component is a module that contains all of the HTML and JS it needs (and, often, the CSS as well). It also only handles its state or the state of its direct children, making it far easier to track the flow of data.
What do I mean by "state?" State can be any sort of data: for example, whether a checkbox is toggled, whether a user is logged in, what gif is currently selected.
The app we are going to create takes text input from a search field and returns a list of individual gifs. This can be broken down into four separate React components, as seen here:
App
component is a bucket for the rest of our modules — it will manage our base state and coordinate between the child components underneath it. For this simple version of our application, it will also handle making API calls to request GIFs, although we will quickly see the limitations of this approach and refactor it in part 2 by adding ReduxSearchBar
component will watch for a change to the user input and pass the search term(s) back up to the App
component when neededGifList
will map the array of gifs returned from the Giphy API and handle rendering individual GifItem
sGifItem
s will display the gifsTo see how we can render data with our first component, let's start building our App
.
First, go to the index.html
file in the root of your app and replace its code with the following:
index.html
Now we get to one of the coolest things about React: this is the only HTML file we need. Remember how we mentioned earlier that React involves writing HTML inside of JavaScript? React will hook into the <div>
element we created and handle rendering our entire application inside of there. Neat, right?
Let's build our first simple React component:
src/index.js
If you run $ npm start
and load up http://localhost:3000
in your browser, you should see our 'Hello World' message on your screen.
There's a lot going on in this code, especially if you're not used to ES2015, so let's break it down piece by piece:
import
is the ES6 version of require
. We are pulling in two libraries here: the base React library, which gives us the code we need to create and manage components, and React DOM, which helps us manipulate elements within the browser. Until recently, they were part of the same package, but the React team split them in two to make it easier to render React on different environments (such as on mobile with React Native.)
Here, we're creating an App component that will serve as the parent for the rest of our application.
The class
constructor is another new feature of ES2015. While this looks like a completely new object-oriented way to write code in JavaScript, similar to what we might find in a language such as PHP or Ruby, it's actually little more than syntactical sugar for creating a plain old JavaScript object. If we were writing ES5, we would create a React class like this:
var App = React.createClass({});
The class
constructor may feel natural to you if you've used another object-oriented programming language, or it may feel weird if you're used to traditional prototypal JavaScript. I encourage you to give it a chance, though; it can help a lot with organizing code in a large-scale project, especially if you're using an IDE like Webstorm.
This is where the magic happens. React's render
function allows us to output JSX, which is syntax falling about halfway between XML and HTML that creates JavaScript objects for us.
The code above in JSX is equivalent to this code in native JavaScript:
return React.createElement( "div", { "class": "greeting" }, React.createElement( "p", { "class": "greeting-text" }, "Hello World!" ));
Babel, one of the libraries included with the Create React App tool, handles this transformation for us. As you can see, JSX is much easier to write!
And finally:
Remember how I mentioned earlier that React-DOM handles any browser-specific manipulation that React needs to do? In the ReactDOM.render()
method — not to be confused with the render()
method in React.Component
— we link our App component with the empty <div id="app"></div>
in our index.html
file.
Let's go ahead and create our Search Bar component within a new src/components
folder. For now, let's just output a <h1>
so we can be sure we're pulling it into our main App
component correctly.
src/components/SearchBar.js
Most of this looks very similar to the App
component we already created. The major difference is the last line, export default SearchBar
, which is another new ES2015 feature.
ES2015's export
works similarly to the module.exports
functionality of ES5. The export
makes our SearchBar
available to import by other pieces of our application, as we are about to do in our App
class.
The default
means that this module is only exporting one value — in this case, the class SearchBar
.
Next, let's update our index.js
file to bring in our first child component.
src/index.js
We are doing two important things here:
SearchBar
class from ./components/SearchBar
at the top of our filerender
method as a self-closing component. (Please note that <SearchBar>
alone is not valid JSX; if the component does not have a body, it is important to add the />
to close the tag.)We now should see this in our browser:
Let's refactor this code to be more interactive. We need to create an input field in our SearchBar component and be able to track what term we are searching for in our main App component so that we can use it to search the Giphy API.
Before we do this, though, we need to let our React components know they should keep track of our term. We can do this by setting it as part of the state of our application. State is the data that will change over time; we want to initialize our state with a default value and then watch for any updates. Every single time React notices a change in state, it will re-render with the value of the new state.
Let's look at how we might do that with our search bar, and then we'll walk through the code piece by piece:
src/index.js
src/components/SearchBar.js
Let's start by taking a closer look at the updated SearchBar
component.
We added a constructor()
method at the top of the class. Similar to what you might find in other OOP languages, the constuctor runs automatically when the class is initialized. Calling super()
within that method lets us access this.state
within the constructor, since our class is a subclass of React.Component.
We are already extending React.Component
when initializing our SearchBar
class, but if we want access to React's this.state
in the constuctor, we need to make sure that our class is inheriting whatever properties are inside of the constructor of the parent ReactComponent
.
We are also initializing our state here, letting our application know that it needs to be aware of our search term. For now, we're setting it to an empty string, but we'll want to be able to update it through our input field.
For now, let's hold off on talking about our onInputChange()
method and take a look at what's happening in our render()
.
Why are we using className
instead of class
here? Remember that we're writing JSX, not HTML. class
is a reserved keyword in JavaScript, so we must use className
instead to set HTML classes. (The same thing is true about for
: if your HTML element requires for
, you should use htmlFor
instead.)
Every time we update our input, React's onChange
property automatically fires.
You probably noticed the odd-looking syntax within the curly braces. This new ES2015 feature is known as fat-arrow functions; they're a stripped-down way of writing functions, similar to what you might find in CoffeeScript.
A fat-arrow function like this:
e => this.onInputChange(e.target.value)
is equivalent to the following code:
function(event) { this.onInputChange(event.target.value);}
Here, we're telling React that every time it notices we've changed our input, it should fire an onChange event and pass the value — our search term — to our onInputChange()
class method. Let's take a closer look at that now:
We now know that our onInputChange()
method will be fired every time the input is changed. You might be wondering, however, why we are calling this.setState
instead of setting the term directly on this.state
, as we did in the constructor.
This is one of the most important things to remember when you're working with React. When you're initializing state in the constructor, you can set it directly with this.state = {}
. However, if you want to signal to React that the state has changed so that it knows to re-render, you need to call this.setState()
instead. This means you should never call this.state = {}
outside of a class constructor.
But where does our this.props.onTermChange(term);
come from? We need a way to pass data from a child component (SearchBar
) to its parent (App
), and in React, we can do that through props -- data or callbacks passed from a parent component. To see how we can do that, let's take a look at the code we added to our index.js
file:
In our <SearchBar />
within index.js
, we are setting a new property called onTermChange
. Whenever we set a property on a child component in this way, it becomes available within that child component via this.props
. In this example, we are using the onTermChange
property to pass the handleTermChange()
callback from our App our SearchBar.
Confused? Let's break down what is actually happening, step-by-step, from the time we open the app in our browser:
App
component renders our SearchBar
component with a prop of onTermChange
, passing in its own handleTermChange
method as an argumentSearchBar
calls its constructor()
method upon initialization. It creates a new state object and sets the term
propery to an empty string.onChange
method on the input, automatically passing in an event
object as an argument. Within its callback, we call our SearchBar
class's onInputChange
class method, passing through the event
object.SearchBar
's onInputChange
method calls this.setState
to update the state's term
property. It also calls the App
component's handleTermChange
method, which is passed through the onTermChange
prop.handleTermChange
methodTo test that this is working, open your developer console within the browser and enter some text into the search bar. You should see the console log the new term every time you add or remove a letter.
It's time to start building the components that will handle displaying gifs! Let's take another look at our app mockup:
There are two different components we will need to build here: a GifList
that maps through the array of gifs we will receive from the Giphy API and a GifItem
to actually render the individual gifs.
Before we actually worry about calling the Giphy API, let's mock up some fake data to pass to our components. In index.js
, add a constructor function and initialize the state with an array of gifs. Don't forget to call super()
so that we actually have access to this.state
! We're also going to create a GifList
component and place it directly below our SearchBar
, passing through the gifs
object we set on our App
's state as a prop.
src/index.js
Next, we need to create our GifList
component. Its sole job is to accept a list of gifs from the App
component, loop through them, and render an individual GifItem
for each object in the array. Let's take a look at the code:
src/components/GifList.js
You might have noticed that this component looks different than the SearchBar
and App
. Instead of using class GifList extends React.Component
like we did before, we're instead writing const GifList = (props) => {}
.
This is called a stateless functional component. We can use these whenever our component does not need to actively track or modify our application's state — in fact, if you're writing idiomatic React code, most of your components will be written this way. The parent component — in this case, App
— passes in all of the data our GifList
needs via its props
. The GifList
only needs to worry about how to display this data.
Once again, we are seeing the new ES2015 syntax for writing functions. We are also seeing the new ES2015 keyword const
, short for "constant", which allows you to declare variables that won't be reassigned.
In short, this:
const GifList = (props) => {}
is the equivalent of this:
var GifList = function(props) {}
Within this function, we are looping through the array of gifs passed down from state. For each gif, we are rendering a GifItem
component.
Notice that we are setting a key
property on each GifItem
. In React, if you have multiple components of the same type, you should give each a unique key.
Why do we care about this? When React manipulates the virtual DOM, it tries to update as few modules as it can. If it can't tell GifItem
components apart, it will need to re-render all of them whenever an update is made.
Our GifItem
component is even simpler:
src/components/GifItem.js
Once again, we are creating a stateless functional component here. Our GifItem
takes the image object passed as props from the GifList
and passes the URL into an image element.
Now, if we check http://localhost:3000
, we should see our three images rendering on the page beneath our search bar:
Obviously, our search engine isn't going to be very exciting with hard-coded data! Let's take a look at how we can call the Giphy API.
While there are a lot of ways to make HTTP requests within the Node.js ecosystem, a library called SuperAgent is one of the best-documented and easiest to use. Run $ npm install --save superagent
to pull it into your project.
Back in our index.js
file, clear out the hard-coded list of gifs in our constructor. We do still want to initialize a gifs
object within this.state
so our application is aware that it needs to track changes, but we can set it to an empty array.
src/index.js
If you take a look at the Giphy API Docs, they walk you through making an API call to an endpoint. We will need three things in order to accomplish this:
https://api.giphy.com/v1/gifs/search?q=
Since we are already receiving a search term from our input as an argument in our handleTermChange
method within index.js
, we should have everything we need to make API calls.
Let's take a look at how we might do that. For now, let's console.log
the first gif that comes back so we know what sort of data we're working with:
src/index.js
After importing SuperAgent's request
object, we want to make our API call within our handleTermChange
method, since that's receiving the term
from our SearchBar
.
We're setting a const named url
with all three things we said we needed to make an API call. Notice the ${term}
in the middle of that string; this is yet another bit of ES2015 sugar. Adding ${}
to a string allows us to concatenate a variable, but in order to use this syntax, you must wrap the string in backticks ``
instead of single or double quotation marks.
Finally, we're making a GET
request using SuperAgent to the URL we specified. You can find out more about how to make requests with SuperAgent in their documentation, but for now, we're going to keep it as simple as possible.
If you search for test
in the search bar, you should see in your console something like this:
So we now know that we will pass an array of objects like this into our GifList
, and our GifItem
will need to use these attributes to render a gif.
Let's refactor our code to pass the Giphy data into our components via this.setState
. Additionally, we are going to make one more update. If we are searching for multiple terms, they will probably be separated by a space; however, if we are passing them to the Giphy API, they will need to be separated by a +
character. Instead of passing just term
to our url
const, let's write a regex to replace all spaces with +
's, like so: term.replace(/\s/g, '+')
.
src/index.js
But wait! If we try to search again, we will see the following error in our console:
bundle.js:8088 Uncaught TypeError: _this2.setState is not a function
Why is this? When we are using ES2015, React does not automatically assume that event handlers we pass as properties (such as onTermChange
in our SearchBar
) are bound to the main component object. (While this bit of automagical binding can be useful 99% of the time, if you're passing in event handlers from outside of your main React application, it can be incredibly limiting. They decided to follow the same semantics as regular ES2015 classes, binding this
to its own instance rather than autobinding to the parent component. This is also why we need to call super()
in our constructors!)
TL;DR: this means that this.setState
is actually being called on onTermChange
, which does not have access to the React.Component
methods inherited by our App
component.
This is one of the more confusing gotchas involved in working with React, but there are two ways around it. Since you'll see both of them out in the wild, let's look at them both:
**Binding in the Constructor**
The first workaround is to explicitly tell our app that the lexical this
of the handleTermChange
method is bound to App
, not onTermChange
. We can do this with the following code in our constructor:
src/index.js
**Fat-Arrow Functions**
We've seen ES6's fat-arrow functions syntax a few times already, but one of the great things about it is that it doesn't introduce its own this
. Because it inherits the lexical this
of its parent scope, we don't run into issues with binding. (This is also why we didn't see this issue in our SearchBar
component.)
If you're going to use fat arrow functions, you can write them two different ways:
As a class method:
src/index.js
As part of the handler:
This can be confusing, especially if you haven't run into a lot of scoping issues with this
in JavaScript before, but if you see this error you should hopefully know how to fix it!
Moving on, we'll need to update our GifItem
component to handle the data from Giphy instead of our hard-coded gif objects. If you take a look again at the objects being returned, you can see that the main gif object has an images
property that contains quite a few different size options. Because we don't want any massive images loading, let's use the downsized
object.
src/components/GifItem.js
Now, if we search for test
, we should see something like this:
It's ugly, but it works! Let's add a bit of styling next.
If you read Unpacking Webpack, you should already be somewhat familiar with handling CSS in Webpack. We are able to import our CSS directly into our JavaScript, and best of all, Create React App handles all of the configuration for us!
Included below is the styling we'll be using. If you're at all familiar with CSS, there shouldn't be anything too confusing here:
src/styles/app.css
We'll also want to import app.css
into our index.js
file so that Webpack knows to bundle our CSS with our JavaScript:
src/index.js
Finally, we'll want to add CSS classes to our GifList
and GifItem
components. Remember that we need to use className
, not class
, since our render
method contains JSX and not HTML! We are also going to use div
s instead of ul
s and li
s, since we're using more of a Masonry-style layout.
src/components/GifList.js
src/components/GifItem.js
Now, if we search for test
again, we should see a (slightly more) nicely-styled list:
There's one more step in part one of this tutorial! Since we're getting all of this metadata back from Giphy, let's add a modal pop-up whenever the user clicks on the gif.
One of the great things about the modularity of React components is how easy it is to plug-and-play third-party packages. While we could go through the trouble of writing our own modal from scratch, we're instead going to pull in react-modal and let it handle most of the heavy lifting for us.
To pull the package into our project, run $ npm install --save react-modal
.
Currently, we have two pieces of state we're tracking in our application. Our App
component is responsible for handling an array of gifs, and our SearchBar
component tracks the search term entered in the input.
For our modal, there are two additional pieces of state we need to track:
We also know that we are going to need to handle two interactions: when the user clicks on a GifItem
component to open the modal and when they click the "close" button to close the modal.
In our index.js
file, let's add the new code we'll need to be able to track our state:
src/index.js
As we did with the gifs previously, we are creating empty objects in this.state
within our constructor so that our app can track the relevant data. We're also trying to approximately match the type of objects within our state — we know gifs
is an array, selectedGif
is a single object, and modalIsOpen
is a boolean, so we can initialize them accordingly.
We are then creating two methods to open and close our modal for us. The openModal()
method will accept our selected gif image as an argument, and closeModal
sets it to null once again.
If you take a look at the react-modal docs, you can see that they provide us with a sample modal component to get started:
<Modal isOpen={bool} onRequestClose={fn} closeTimeoutMS={n} style={customStyle}> <h1>Modal Content</h1> <p>Etc.</p></Modal>
We could put this directly into our App
component, but in the spirit of modularity, let's create a separate GifModal
component as a wrapper for the Modal
component provided by react-modal
.
Because this component won't need to track state — our App
will be handling whether or not the modal should be open and passing this data in as props — we can make this a functional component.
src/components/GifModal.js
Working directly off of the sample Modal
component in the react-modal
docs, we can see that we need at least two properties: isOpen
and onRequestClose
. Because onRequestClose
takes a function, we'll need to pass a handler down from our App
component and tie it to the closeModal
method we already created.
We're also adding a bit of basic JSX here to display the full-sized gif (not the downsized one we're using in GifItem
) along with the source and rating coming back from the Giphy API.
Going back to our index.js
file, let's plug in our GifModal
component:
src/index.js
Here, we're passing in our our modalIsOpen
and selectedGif
objects from state (even though we don't yet have a way to open a modal or select a gif.) We're also passing in our closeModal()
method via a prop; whenever our third-party Modal
component calls onRequestClose
, such as when clicking outside of the modal or hitting the "close" button, it will call our App
component's closeModal()
function.
But when we take a look in the browser, we see the following error:
Uncaught TypeError: Cannot read property 'images' of null
Digging into our bundle.js
file, we can see that it's looking for props.selectedGif.images
and throwing an error because the images
property does not exist on the current null selectedGif
. We know we occasionally want selectedGif
to be null, though, so let's add a check at the beginning of our component to return an empty div if this property doesn't exist:
src/components/GifModal.js
That should fix the error!
Next, we need our App
component to know when a gif is clicked so that it can set selectedGif
and modalIsOpen
on state accordingly. Once again, we're going to have to pass a handler down into a child component as a prop, but this time, we'll have to do it twice, first to GifList
and then to GifItem
.
Let's start from the initial click event. On our GifItem
component, we will need an onClick
handler to pass the component's gif
prop to a handler sent all the way down from App
. Let's call that prop onGifSelect
.
We're going to make one more update as well. Currently, our GifItem
is only expecting a prop object called image
. Because image.onGifSelect()
doesn't make as much sense, we're going to do a bit of ES2015 shorthand to assign any objects underneath image
to their own vars.
So instead of:
const GifItem = (image) => {
which gives us image.gif
and image.onGifSelect
, we can write:
const GifItem = ({gif, onGifSelect}) => {
If we do that, we can access our props as gif
and onGifSelect
directly!
Moving on, we know we need to pass onGifSelect
as a prop from GifList
to GifItem
:
src/components/GifItem.js
src/components/GifList.js
We don't need a lot of code to accomplish this — GifList
will expect an onGifSelect
prop passed down from the App
component, but ultimately, it's not concerned about what else is being passed back and forth.
Finally, back in index.js
, we need to add the onGifSelect
handler and pass in our openModal
method:
src/index.js
There's a lot to process here. Let's slow down and go through the steps from the time the user clicks on a gif to open a modal:
GifItem
component receives an onClick
event anywhere in its main <div>
gif
prop as an argument into its onGifSelect
propGifList
receives the selectedGif
. It, too, has an onGifSelect
prop, and through this handler, it passes the selectedGif
to its parent componentApp
component receives the selectedGif
and passes it into its openModal
methodopenModal
method sets the selectedGif
on state and also sets modalIsOpen
to true
modalIsOpen
and selectedGif
are props on the GifModal
component, when React calls this.setState
(not this.state = {}
!), it knows it needs to re-render GifModal
GifModal
receives modalIsOpen
and selectedGif
as props, along with an onRequestClose()
handlerGifModal
passes modalIsOpen
and onRequestClose()
as props to the third-party Modal
component and renders the selectedGif
GifModal
's onClick
handler fires, calling the closeModal()
function passed down from App
selectedGif
is set to nullGifModal
is re-rendered due to the new state and, because it has received an empty object for props.selectedGif
, returns an empty divWe have two more small updates to make! First, let's adjust the styling on our GifModal
so that it's not quite as ugly:
src/components/GifModal.js
src/styles/app.css
Finally, let's add some placeholder text on our SearchBar
to give the user some idea of what to do:
src/components/SearchBar.js
Now, when you search for a gif and open the modal, you should see something like this:
That wraps it up for part one!
While vanilla React projects can be fine if you're working at a relatively small scale, I'm sure you can see even from this little project how easy it would be for state to get out of control when it's sprinkled all over the place. In part two, we're going to refactor this project to use Redux, a library that allows you to store the entire state of your application within a single object tree.
Check back soon, and as always, we'd love to hear any thoughts/comments on Twitter.
We appreciate your interest.
We will get right back to you.