This is a second post that walk you through building a Blockchain DApp, using truffle, React, Next.js and MobX.
As a quick reminder we’re going to use the Blockchain (Ethereum) as an efficient way to handle the mapping of a user’s and their favorite tv shows.
The app displays a list of shows coming from rotten tomatoes unofficial API. Split into three categories: Fresh, New and Popular.
A special section in the app “Bookmarks” displays a list of user’s favorite shows which are currently stored on the Blockchain.
Users are able to add or remove shows from their Bookmarks section. Every time a user adds or removes a show a Smart Contract function is invoked and the change is stored on the Blockchain.
The front-end app is a server side rendering React app powered by Next.js, for state management we’ll use MobX, styling and layout will come from the awesome tachyons project and in order to interact with the Blockchain we’ll write a Smart Contact in solidity, write tests for it in JavaScript and deploy it locally using truffle.
In the previous post, we went over the implementation of our Smart Contract that acts as our app backend logic and storage. In this post we’ll focus on building the UI using React and how we can invoke actions on our Smart Contract from within the browser.
In more details in this post I’ll go over:
Make sure you have node, git and nvm install before starting this.
First we need to install TestRpc and Truffle:
npm install -g ethereumjs-testrpc
npm install -g truffle
Now clone the app and install the dependencies:
//clone the repogit clone [email protected]:liors/tvdapp.git
// Navigate to within the directorycd tvdapp
//install dependenciesyarn install
//start the development serveryarn dev
In a new terminal window start a local Blockchain network:
testrpc
Now we need to compile and migrate our Smart Contract run:
yarn run blockchain
Once you run all the above commends you can open your browser at http://localhost:3020/tv/fresh and see a local version of the app.
Next.js comes with default express configuration. In some cases that’s fine, in our case I wanted to add routes to our API requests so I’ve used a custom express server.
Under /server you can find two files index.js and api.js
Let’s go over index.js
Our API has 3 end-points:
All of them uses the same api.get function we exports from api.js. In addition we alias some get requests to our main /tv/fresh route, so when a user visits / or /tv we redirect him to our /tv/fresh page. All other requests i.e., ‘*’ will be handle by next.js default route.
The last part of our backend code is fetching the content from rotten tomatoes and enrich the return data with images coming from tvmaze.
Let’s go over api.js
We export a get function that receives a type. We then making a request and map the result over to generate an array of titles and urls.
IMO tvmaze provides better quality images, so let’s enrich our API by getting for each show an array of images.
Async/await syntax helps us avoid nested callbacks and hopefully to create a more readable and clean code.
Our get function returns a Promise that includes an array of JSON objects, each includes a title and an img object.
Next.js provides a very simple routes API. You only need to create a pages folder and each file under this folder will become the main react component rendered for this route. For example /pages/index.js will be your app homepage i.e., the default route /.
In our case the main page route is under /pages/tv/fresh.js
Next.js also provides a way to fetch data on both server and client by introducing a new life cycle function called getInitialProps. When implementing it we must return a Promise
that resolves to a JavaScript object, which then populates the component’s props.
We start by invoking our API — /api/tv/fresh and initiating our store with the result shows. The function then return our component props: shows and if we are rendering the results on the server or not.
Our render function is quite simple — we simply render a Page component and pass it two props: type and store.
All our pages/routes look very similar, the differences are the page type and store which contains the relevant shows for the given type.
Let’s look at our Page component.
This component uses two additional components — our app navigation and the grid of shows. The selected page is the current prop type. Our render function is wrapped with a MobX provider as this is our main component of the app.
This component has another responsibility. Only when running on the client i.e., in React’s componentDidMount
function we setup our web3 instance, once this is done we make a request to our Smart Contract to get the already bookmarked shows. Once this request is resolved we update our store which cause our app to be re-rendered if needed.
Lets looks at our main UI component shows.js
Our render function uses a `square title subtitle` layout from tachyons.io. In order for our app to recognize these css classes (for example cf, w-100, pa2-ns etc) we need to include tachyons css file.
Next.js provides a way to customize the main html that is being generated by the server by creating a _document.js which let you return a chunk of html that includes a head tag.
In our cases we’ll set the viewport and include some external resources we use — font, css files and the web3 library.
<Head><meta charSet='utf-8' /><meta name='viewport' content='initial-scale=1.0, width=device-width' /><link href='//fonts.googleapis.com/css?family=Roboto' rel="stylesheet" /><link href='//cdnjs.cloudflare.com/ajax/libs/tachyons/4.8.1/tachyons.min.css' rel='stylesheet' /><link href='//cdnjs.cloudflare.com/ajax/libs/nprogress/0.2.0/nprogress.min.css' rel='stylesheet' /><script src='/static/js/web3.min.js'></script></Head>
Back to our shows component — once we render our shows we need to display a different SVG for shows that are already bookmarked vs those that are not. We do that by looking at our bookmarked shows array in our store:
isBookmarked = show => some(this.props.store.bookmarkedShows, show)
some is a lodash function that checks if predicate
in this case identity returns truthy for any element of collection
in our case our list of shows.
Now we need to implement the Bookmark action. We do that by wrapping the title/subtitle/bookmark SVG elements w/ an anchor tag and define an onClick function on it:
handleBookmark = show => this.props.store.bookmark(show)
Our last react component is the Notification component that gives to the user an indication that a show is added/removed from the Blockchain, you can find it under /components/notification.js
As we saw in our components implementation we do not have anythis.setState()
calls to re-render our components or fetch requests to get/change data. Our MobX store handle this for us and provide us w/ the core APIs our UI needs.
Our store supports the following actions:
Let’s look in more details at our bookmark function.
We first check if the provided show is already bookmarked — if not we use our blockChainService’s bookmarkContract function. As an argument this function gets a JSON that represents the show to bookmark— this JSON will be then stored on the Blockchain.
Once the request is resolved we add the show to our observable shows array, turn on the notification flag and set the proper type of action — this will help the notification component to display the proper text.
To remove/reject an already bookmarked show we call rejectBookmarkContract and filter out the show from our observable shows array.
We got to the last part of our app, the service that uses ethereum’s web3 to invoke remote function on our Smart Contract. Let’s look how the service getBookmarks is implemented:
let getBookmarks = function () {return new Promise((resolve, reject) => {let instancegetBookmarkInstance().then(result => ({instance} = result)).then(() => instance.getBookmarks.call()).then(bookmarks => {resolve(bookmarks && JSON.parse(bookmarks.toString()))})})}
In order to invoke any Smart Contract function, we need to get the available web3 instance. We do that by importing our BookmarkArtifact and the contract function from truffle-contract
import BookmarkArtifact from '../build/contracts/Bookmark'import contract from 'truffle-contract'
Then we provide the contract function our artifact and set it’s Provider to the current web3 instance current provider.
const Bookmark = contract(BookmarkArtifact)Bookmark.setProvider(web3Instance.currentProvider)
Once we did that we can get the accounts and return the deployed instance for the first one.
web3Instance.eth.getAccounts((error, accounts) => {const account = accounts[0]Bookmark.deployed().then((instance) => {resolve({ instance, account })})})
Ok, so back to out getBookmarks function. Once we have an instance we can invoke our Smart Contract public APIs. In this case we need to get our bookmark shows i.e., call getBookmarks
instance.getBookmarks.call().then(bookmarks => {resolve(bookmarks && JSON.parse(bookmarks.toString()))})
The result will be a string that we parse and transform into a JSON object.
Bookmarking a show looks very similar. We first get an instance and then call our bookmark function, in this case we need to provide some addition information — from which account this action happens.
instance.bookmark(JSON.stringify(blockchainBookmarks), { from: account }).then(showId => instance.getBookmarks.call()).then(bookmarks => {resolve({ data: JSON.parse(bookmarks.toString()) })})
That’s it our app is complete.
To summarize, we’ve been using React, Next.js, MobX and truffle to build a server side rendering React DApp. I hope you can take this simple setup to start your own development on top of the Blockchain.
The complete code is available on github, if you like it please start the repo!
A live version of the app is deployed here — Please make sure your browser can run DApps, I’ve been using MetaMask chrome extension.
Thanks for reading. Hope this is helpful.