Before starting: Please don’t ask me why I put up those heavenly, mouth-watering desserts. I couldn’t help it. Truffle guys chose very yummy names. I am sure these photos will make dev part more bewitching! 😉
This article is inspired by Adrian Li’s contribution to Truffle framework. Adrian’s work can be found here. It is highly recommended that you do give it a read!
Source: https://giphy.com/gifs/chris-pratt-jimmy-kimmel-rVbAzUUSUC6dO
Drizzle was added to Truffle Suite in February 2018. It is Truffle’s first front-end development tool. Of course, it is here to make developers life a lot easier. It makes use of React under the hood.
In this tutorial, true power of Drizzle will be demonstrated from scratch. It is probably the best way to gauge potential of a framework with gaining knowledge about how the entire mechanism functions under the hood.
npm install truffle -g
. To check if it is installed properly, type truffle version
on your terminal.npm install ganache-cli -g
. Ganache will be used to spin up a personal, local Ethereum blockchain with dummy accounts for dev purpose.npm install create-react-app -g
. Set up a modern React web app with a single command. Thanks to Facebook.Step 1.1: Create your dev folder and move into it.
$ mkdir drizzle-tutorial$ cd drizzle-tutorial
Step 1.2 : Initialise truffle
$ truffle init
Output will look like this:
Downloading...
Unpacking...
Setting up...
Unbox successful. Sweet!
Commands:
Compile: truffle compile
Migrate: truffle migrate
Test contracts: truffle test
This operation creates a basic project structure. Open drizzle-tutorial
folder in any text editor.
**contracts/**
: Directory for Solidity smart contracts**migrations/**
: Directory for deployment files**test/**
: Directory for test files for testing your application and contracts**truffle.js**
: Truffle configuration file (will be deprecated soon)**truffle-config.js**
: Truffle configuration fileOpen the terminal and run following command:
ganache-cli -b 2
-b
stands for block time which is 2 seconds. In other words, this personal blockchain mines a new block every 2 seconds. This creates a fake time lag between when you request to commit a transaction and when it is actually committed. By default, Ganache does it instantly.
This process runs on http://127.0.0.1:8545 by default. You can open up this RPC on metamask as well.
Step 3.1: Create a new file named **Addition.sol**
in the **contracts/**
directory.
pragma solidity ^0.4.24;
contract Addition {int public sum = 0;
function add (int x, int y) public {sum = x + y;}}
Step 3.2: To tell truffle where our personal blockchain (Ganache) is running, we have to do the following
In truffle.js
(if you’re on windows truffle-config.js
) do the following changes.
module.exports = {networks: {development: {host: "localhost",port: 8545,network_id: "*" // Match any network id}}};
Step 4.1: Compile smart contract
Fire up your terminal and run the command:
truffle compile
Following is the output of the command:
Compiling ./contracts/Addition.sol...
Compiling ./contracts/Migrations.sol...
Writing artifacts to ./build/contracts
Step 4.2: Migrate (deploy) them to our personal blockchain
Create a new file named **2_deploy_contracts.js**
in the **migrations/**
directory.
const Addition = artifacts.require("Addition");
module.exports = function(deployer) {deployer.deploy(Addition);};
In the terminal, run following command: truffle migrate
. Make sure ganache is running. Also, make sure Solidity compiler used is up to date.
Following is the output of the above command:
Running migration: 1_initial_migration.js
Deploying Migrations...
... 0x8c5942b90b785a51ccfe55dcbbc62e0f5469484b72f0ef5ee5fe21e8cef91d3c
Migrations: 0x980b663f1cedfad76349e0c4da1e0c7405b1fd23
Saving successful migration to network...
... 0xe3b1ad2a400d341d894170b8930471ee6d7fcb50314993df3418b4b3a517ba03
Saving artifacts...
Running migration: 2_deploy_contracts.js
Deploying Addition...
... 0x1629b636f1457d91350d5e069ca6a9e88fde72e3438ad9f6abb83c5dfa4675af
Addition: 0x558c220aac56c27a21215bd77551a51e0070d1b6
Saving successful migration to network...
... 0xb3530be9102f4b2d914fd07dcdd51d10d7cbc496aab28b278ec35e4e4b476af3
Saving artifacts...
To validate if everything is working all right, let us Mocha test our contract.
Create a new file named **Addition.js**
in the **test/**
directory.
const Addition = artifacts.require("./Addition.sol");
contract("Addition", accounts => {it("should display 5", async () => {const addition = await Addition.deployed();await addition.add(3, 2, { from: accounts[0] });const storedSum= await addition.sum.call();
assert.equal(storedSum, 5);});});
Run the test using the command: truffle test
. Following is the output:
Awesome! Now we know that the contract actually works.
To bootstrap a minimalistic react project, run the following command: npx create-react-app client
. This may take a couple of minutes.
This command will create client
.
Output will look like this. (This is not complete output because its hella long).
Creating a new React app in /Users/niharikasingh/Desktop/playground/drizzle-tutorial/client.
Installing packages. This might take a couple of minutes.
Installing react, react-dom, and react-scripts...
yarn add v1.10.0
info No lockfile found.
[1/4] 🔍 Resolving packages...
warning react-scripts > eslint > file-entry-cache > flat-cache > [email protected]: CircularJSON is in maintenance only, flatted is its successor.
warning react-scripts > jest > jest-cli > prompts > [email protected]: Please upgrade to kleur@3 or migrate to 'ansi-colors' if you prefer the old syntax. Visit https://github.com/lukeed/kleur/releases/tag/v3.0.0\\ for migration path(s).
[2/4] 🚚 Fetching packages...
[3/4] 🔗 Linking dependencies...
[4/4] 📃 Building fresh packages...
success Saved lockfile.
success Saved 828 new dependencies.
Now let’s cd client
. We have to link our smart contracts with this React project. We can copy the /build/contracts
folder in client
folder directly or use the following command:
cd srcln -s ../../build/contracts contracts
Before running the installation command, make sure you’re in the client
directory of your project. To do that, run cd ..
in the terminal.
To install Drizzle, run the following command which took me about 3 minutes to be precise:
npm install drizzle --save
AND NO OTHER DEPENDENCY! How cool is that? No need to install Web3.js or anything else. Drizzle contains everything we need to work with our smart contracts.
Run the React front-end on port 3000. To do this, run npm start
. And point your browser to http://localhost:3000. The following page should come up.
We have to make some changes in **client/src/index.js**
first.
import React from "react";import ReactDOM from "react-dom";import "./index.css";import App from "./App";
// import drizzle functions and contract artifactimport { Drizzle, generateStore } from "drizzle";import Addition from "./contracts/Addition.json";
// let drizzle know what contracts we wantconst options = { contracts: [Addition] };
// setup the drizzle store and drizzleconst drizzleStore = generateStore(options);const drizzle = new Drizzle(options, drizzleStore);
// pass in the drizzle instanceReactDOM.render(<App drizzle={drizzle} />, document.getElementById("root"));
Note that the **drizzle**
instance is passed into the **App**
component as props.
Step 9.1: Adding state variables
We will do is to add the following line inside our App component:
state = { loading: true, drizzleState: null };
We are going to be using two state variables here:
**loading**
— Indicates if Drizzle has finished initializing and the app is ready. The initialization process includes instantiating **web3**
and our smart contracts, fetching any available Ethereum accounts and listening (or, in cases where subscriptions are not supported: polling) for new blocks.**drizzleState**
— This is where we will store the state of the Drizzle store in our top-level component. If we can keep this state variable up-to-date, then we can simply use simple **props**
and **state**
to work with Drizzle (i.e. you don't have to use any Redux or advanced React patterns).Step 9.2: Writing some initialisation logic
componentDidMount() { const { drizzle } = this.props; // subscribe to changes in the store this.unsubscribe = drizzle.store.subscribe(() => { // every time the store updates, grab the state from drizzle const drizzleState = drizzle.store.getState(); // check to see if it's ready, if so, update local component state if (drizzleState.drizzleStatus.initialized) { this.setState({ loading: false, drizzleState }); } });}
Step 9.3: Unsubscribing from the store
compomentWillUnmount() { this.unsubscribe();}
This will safely unsubscribe when the App component un-mounts so we can prevent any memory leaks.
Step 9.4: Modify render() method
render() { if (this.state.loading) return "Loading Drizzle..."; return <div className="App">Drizzle is ready</div>;}
The final file should look like this:
import React, { Component } from 'react';import logo from './logo.svg';import './App.css';
class App extends Component {state = { loading: true, drizzleState: null };
componentDidMount() {const { drizzle } = this.props;
// subscribe to changes in the storethis.unsubscribe = drizzle.store.subscribe(() => {
// every time the store updates, grab the state from drizzleconst drizzleState = drizzle.store.getState();
// check to see if it's ready, if so, update local component stateif (drizzleState.drizzleStatus.initialized) {this.setState({ loading: false, drizzleState });}});}
compomentWillUnmount() {this.unsubscribe();}
render() {if (this.state.loading) return "Loading Drizzle...";return <div className="App">Drizzle is ready</div>;}}
export default App;
After this, localhost:3000 should look like this:
Create a new file at **client/src/ReadSum.js**
and paste in the following:
import React from "react";
class ReadSum extends React.Component {componentDidMount() {const { drizzle, drizzleState } = this.props;console.log(drizzle);console.log(drizzleState);}
render() {return <div>ReadSum Component</div>;}}
export default ReadSum;
And then inside **App.js**
, import the new component with this statement:
import ReadSum from "./ReadSum";
Change App.js
render() method to the following:
render() { if (this.state.loading) return "Loading Drizzle..."; return ( <div className="App"> <ReadSum drizzle={this.props.drizzle} drizzleState={this.state.drizzleState} /> </div> );}
Looks like this in action:
Now, let’s actually read the value from our smart contract.
import React from "react";
class ReadSum extends React.Component {state = { dataKey: null };
componentDidMount() {const { drizzle } = this.props;const contract = drizzle.contracts.Addition;
// let drizzle know we want to watch 'sum'var dataKey = contract.methods["sum"].cacheCall();
// save the `dataKey` to local component state for later referencethis.setState({ dataKey });}
render() {// get the contract state from drizzleStateconst { Addition } = this.props.drizzleState.contracts;
// using the saved `dataKey`, get the variable we're interested inconst sum = Addition.sum[this.state.dataKey];
// if it exists, then we display its valuereturn <p>Sum: {sum && sum.value}</p>;}}
export default ReadSum;
If we wish to add two new numbers and store the sum on the blockchain, do the following:
First, let’s create a new file **client/src/SetSum.js**
and paste in the following:
import React from "react";
class SetSum extends React.Component {state = { stackId: null};
handleKeyDown1 = e => {// if the enter key is pressed, set the value with the stringif (e.keyCode === 13) {this.setValue(e.target.value1);}};handleKeyDown2 = f => {// if the enter key is pressed, set the value with the stringif (f.keyCode === 13) {this.setValue(f.target.value2);}};
setValue = (value1, value2) => {const { drizzle, drizzleState } = this.props;const contract = drizzle.contracts.Addition;
// let drizzle know we want to call the `add` method with `value1 and value2`const stackId = contract.methods["add"].cacheSend(this.textInput1.value, this.textInput2.value, {from: drizzleState.accounts[0]});
// save the `stackId` for later referencethis.setState({ stackId });};
getTxStatus = () => {// get the transaction states from the drizzle stateconst { transactions, transactionStack } = this.props.drizzleState;
// get the transaction hash using our saved `stackId`const txHash = transactionStack[this.state.stackId];
// if transaction hash does not exist, don't display anythingif (!txHash) return null;
// otherwise, return the transaction statusreturn `Transaction status: ${transactions[txHash].status}`;};
render() {return (<div><input type="number" ref={(input1) => this.textInput1 = input1} onKeyDown={this.handleKeyDown1} /><input type="number" ref={(input2) => this.textInput2 = input2} onKeyDown={this.handleKeyDown2} /><div>{this.getTxStatus()}</div></div>);}}
export default SetSum;
Import and include it inside **App.js**
import SetSum from "./SetSum";
And change the render()
method of App.js
to:
render() { if (this.state.loading) return "Loading Drizzle..."; return ( <div className="App"> <ReadSum drizzle={this.props.drizzle} drizzleState={this.state.drizzleState} /> <SetSum drizzle={this.props.drizzle} drizzleState={this.state.drizzleState} /> </div> ); }
This should work!
Transaction status can be ‘pending’ or ‘successful’.
And this was the end to Drizzle 101.