2016-09-08

In this tutorial you’re going to create a realtime Pokedex app with React and RethinkDB. The app will allow users to look for a Pokemon by typing on a search field. If a Pokemon is not found, it will ask if they want to add it. If the user chooses yes, it will make a request to the server. The server will then talk to the Pokemon API to look for the Pokemon. If it exists, the server will save the data to RethinkDB. At this point, RethinkDB’s changefeeds feature will be triggered and it will send the new data to all connected clients using Socket.io. This will update the UI to show the newly added Pokemon to all users.

Here’s what the app is going to look like:



No previous knowledge of React or RethinkDB is required for this tutorial. So I’m going to provide as much detail as I can.

Steps We’ll Take in This Tutorial

Install RethinkDB

Setup a RethinkDB database and table

Create the app

Install the Dependencies

Create the ListItem Component

Create the Server Component

The final code for this tutorial can be found in this Github repository

Setting Up Your Environment

If you don’t want to pollute your OS with lots of installs, you can use the Scotchbox Vagrant box to install all the dependencies for this app.

Installing RethinkDB

You can install RethinkDB by following the installation instructions for your OS on the official docs. However, if you have already Docker installed, you can also run the RethinkDB docker image with the following:

docker run -d -P –name rethink1 rethinkdb

Once the image is installed, it will return the container ID, at this point you can run RethinkDB with the following:

docker start <container ID>

Setting Up the Database

Once you got RethinkDB running, access the admin console in your browser by going to http://localhost:32770. If you used vagrant, be sure to replace localhost with the ip address assigned to it.

Click on tables on the main navigation then click on add database. Enter pokedex as the name of the database. Once it’s created, add a new table named pokemon. This will be the table that you’re going to work on throughout the tutorial.

Creating the App

The app is going to have two components: the actual React app and the server component. You’re going to create the app first. This will reside inside the app folder of your working directory. You can check the project on Github to have an idea of the directory structure.

Installing the Dependencies

You can install the dependencies by creating a package.json file and add the following:

Execute npm install to install all of the dependencies. Here’s an overview of what each one does:

aja – for making http requests to the server.

picnic – for beautifying the app.

react – for creating UI components.

react-dom – for rendering React components into the DOM.

react-image-fallback – for showing a fallback image in case the Pokemon images fails to load.

react-loading – for showing a loading animation while saving a new Pokemon data.

react-tooltip – for generating tooltips that shows the Pokemon description.

socket.io-client – for talking to socket.io.

And here’s what each devDependencies does:

babel-preset-es2015 – for transforming es6 code into es5 which can run in all modern browsers.

babel-preset-react – the babel preset for transforming JSX code into plain JavaScript.

babelify – the babel transformer for Browserify.

browserify – puts all the files together in a single file so you can link to it on your page.

You could actually use Webpack to achieve the same thing plus more features such as live-reloading, but to keep things simple, I’m going to leave that for another day.

Once you’re done installing those, create a src and src/components directory. This is where the React components will reside. Inside the src directory, create an app.js file and import the libraries that you’ve just installed:

Inside is the constructor where you create a socket connection to the server component and declare the default state, because this function gets executed once this component is initialized. In React, the state is used to store component data that changes over time. Every time the state is updated, the component is re-rendered to reflect the changes. You can read more about this in Thinking in React.

The componentWillMount() is one of the component lifecycle methods in React. It gets executed right before this component is rendered into the DOM. This makes it the perfect candidate for listening for server updates and fetching the pokemon list from the server.

}

The filterPokemonList() function is responsible for updating the state with a list of filtered Pokemon based on the search term inputted by the user.

}

The getPokemonList() function performs a GET request to the /pokemon route in the server. This route returns an array containing all the Pokemon data that were previously saved to RethinkDB. Once a response comes back, the state is updated using the returned data. The pokemon list and the filtered one is the same at this point since the user hasn’t typed anything on the search field yet.

The render() method is the only required method when creating a component with React. It’s what returns the actual UI of the component. The UI is represented using HTML. And most importantly, a React component can only return a single root element.

Breaking down the code above, you have a text field for entering the name of the Pokemon you’re looking for. The onChange attribute is used to specify the function to be executed when the value of the text field changes. In this case, the searchPokemon function is executed. There’s a need to use bind instead of simply calling it directly because methods in es6 aren’t automatically binded to the class. Below the search field is the container for the Pokemon list. This uses the filtered Pokemon list as the data source for the map function. The map function goes through the Pokemon list and calls the renderListItem() function for every iteration.

Below the Pokemon list is the container for the loading animation. The current value of the is_loading property is used to control the class name to use. A hidden class is added to hide the container if the component is not in the is_loading state.

The renderNoResults() function is called. This function renders the button for adding the Pokemon to the database. It only renders when the filtered Pokemon list contains nothing. The button when clicked calls the savePokemon() function.

The savePokemon() function is the one that’s making a request to the server for saving the Pokemon into the database.

Next is the searchPokemon() function. As you’ve seen earlier, this gets executed every time the user types in something on the search field.

Creating the ListItem Component

As you’ve seen earlier, the ListItem component is used to render each Pokemon data. Create a ListItem.js file inside the src/components directory:

In the above code, aside from Components which you’ve used earlier, PropTypes is also extracted from react. You’ll use this later on to specify which props you expect consumers of this component should specify.

Create the component, this time you need to export it so that it can be used on other files.

The render() function returns a list item. It has a data-tip attribute which is used by react-tooltip for the contents of the tooltip. The tooltip becomes visible on click to cater for touch devices. Below it is the ReactImageFallback component which takes in the Pokemon image, the fallback image in case it fails to load, and an initial image to display while the image is loading. And below that is the Pokemon name and its types. Inside the types div is where the array of types is rendered.

Here’s the renderTypes() function:

The Pokemon type is used as the value for the key because it’s unique.

Define the props that you want to be passed in to this component. In this case there’s only a single one and should be an object.

If this prop isn’t passed in, a warning will show up in the console. If you want to learn more, you can read more about this on the documentation for prop validation.

Next, define the styles css/styles.css:

To put it all together, create the actual app page (index.html):

As you can see, it’s pretty lightweight since all the UI stuff were already defined in React. All of the styles should be linkable since you already installed and created them earlier. The only thing that hasn’t been created yet is the js/app.js file. This is the compiled version of all the JavaScript files that you previously worked on. In the package.json file earlier, there’s the property called scripts:

This is where you can define commonly used npm scripts. In this case, a compile script is defined. This is responsible for compiling all the files inside the src directory to the js/app.js file. Browserify is the main script being used here, -t is used for defining the transforms to be utilized for compiling the app. This is where babelify and its presets comes in. They’re responsible for transforming the JSX and es6 code to JavaScript code that can run in modern browsers.

You can run the compile script using the following command:

npm run compile

Once the script is done, you should have a js/app.js file in your working directory.

Server Component

Navigate outside the app directory, create a package.json file and add the following:

Execute npm install to install all the dependencies. Here’s what each one does:

body-parser – for parsing the request body for any JSON data.

cors – for allowing http requests from any host.

express – for creating the web server.

pokedex-promise-v2 – for easily making requests to the Pokemon API.

rethinkdb – for talking to RethinkDB.

socket.io – this handles real time connection between the server and the user’s browser by means of the WebSocket protocol. We’re using it to send new Pokemon data from the server to the client in real time.

Create a server.js file and add the following:

Connect to RethinkDB and listen for changes on the pokemon table inside the pokedex database. If a change happens (e.g. a row is deleted/updated, a new row is created), loop through the changed data and emit a pokedex_updated event for each one. The row contains an old_val and a new_val. If it’s an insert operation, the old_val is null, if it’s an update operation, old_val contains the value from before the row was updated.

The /save route accepts the name of the Pokemon passed in from the request body. This is used to determine if it already exists in the database.

After that, a request is made to the pokemon endpoint of the API to get the types (e.g. water, electric) of the Pokemon. Here you’re using the pokedex-promise-v2 library to easily get the details that you want. Once a response comes back, construct the object that contains the data that you want and then return it. You will be using promise.all later on to extract this data.

Another request is also performed to the pokemon-species endpoint to get the Pokemon description. Again, extract the data that you want and then return it.

Both requests returns a promise so Promise.all is used to listen for when both promises has been resolved. At this point you know that all the data that you need has been acquired. So you can now save the data to RethinkDB. You can do that by calling the insert method and passing in an array containing the object that you want to save.

The /pokemon route returns all the data from the pokemon table:

Finally, run the server on port 3000:

app.use(express.static(‘public’)); //specify directory for static assets

server.listen(3000); //run server on port 3000

Now you can access the app on your browser and add some Pokemon. For me it’s in http://192.168.33.10/rethinkreact-pokedex/app/index.html.

If you didn’t setup a server, you can just access the html file in the browser.

Summary

In this tutorial you learned how to use React, RethinkDB and Socket.io to create a Realtime Pokedex app. In a nutshell, RethinkDB is a NoSQL database which stores data as JSON documents. It also provides real time capabilities by means of its changefeeds feature. But since RethinkDB is just a database, you needed to use Socket.io in order to propagate the changes to all connected clients.

For further improvement of the app, you can try to add more data from the Pokemon API. And if you’re playing Pokemon Go, you can also add a Pokemon location feature where Pokemon locations are plotted in a Google map in real-time. You would need Geospatial queries for this one which RethinkDB supports by default.

Show more