1 of 60

Learn React

With Austin @bitcoinplebdev

2 of 60

Visit the slides

3 of 60

Topics covered in this lesson

  • Review of our last lesson

​

  • React from a high level

​

  • React components

​

  • Passing props

​

  • React hooks

​

  • Callback functions

​

  • Live demo rebuilding pleb wallet in React

4 of 60

What we learned in development environments

  • Setting up our local dev environment with Visual Studio Code / Node

​

  • Setting up our remote dev environment with Git / Github

​

  • Understanding a React codebase / NPM / package.json

​

  • Setting up our production environment with Vercel / Github

5 of 60

React from a high level

At its very core, React basically maintains a tree for you. This tree is able to do efficient diff computations on DOM elements.

Think of your HTML code as a tree. In fact, that is exactly how the browser treats your DOM (your rendered HTML on the browser). React allows you to effectively re-construct your DOM in JavaScript and push only those changes to the DOM which have actually occurred.

6 of 60

React from a high level

Virtual DOM in React is a “virtual” representation of the actual DOM. It is nothing but an object created to replicate the actual DOM.

Unlike the actual DOM, the virtual DOM is cheap to create because it doesn’t write to the screen. It is only available as a strategy to prevent a redraw of unnecessary page elements during re-render.

​

7 of 60

React components

Components are independent and reusable bits of code. They serve the same purpose as JavaScript functions, but work in isolation and return HTML.

Put simply, React components are functions that may or may not house logic, but ALWAYS return some UI elements.

Components come in two types, Class components and Function components

8 of 60

React class components

Before React 16.8, Class components were the only way to track state and lifecycle on a React component. Function components were considered "state-less".

With the addition of Hooks, Function components are now almost equivalent to Class components. The differences are so minor that you will probably never need to use a Class component in React.

9 of 60

React functional components

As you can see below a functional component is just a standard JavaScript function that returns UI elements

Notice that we are using the JavaScript arrow function syntax for our functions

10 of 60

React component hierarchy

React allows us to break the user interface into logically connected components. When combined these components make a complete application structure.

These components always fall into a hierarchy nested within the primary App component.

When building out components you want to make them reusable while still keeping the minimal amount of logic and functionality in each one

​

​

11 of 60

React component hierarchy

src/components/Header.js

src/App.js

12 of 60

Passing props in React

“Props” is the generic term for any data that is passed down to child components.

Props can be passed in with a props object or “destructured” by defining the specific prop name

13 of 60

Passing props in React

Props can only be passed down to child components in the hierarchy, they cannot be passed from a child component to a parent component.

​

So if we were fetching data from an API that was going to be displayed in the red, purple, and green components we would need to fetch that data in the orange component and pass it down.

�Otherwise we would have to fetch the data three different times in each of the child components

14 of 60

React hooks

Hooks are built-in React functions introduced in React version 16.8. They allow you to use features of the React library like lifecycle methods, state, and context in functional components without having to worry about rewriting it to a class.

Each React Hook name is prefixed with the word "use". For example, useState or useEffect. This format was chosen because Hooks let developers use the special features of the React library. So you're using that special feature of the React library.

​

15 of 60

useState hook

The useState Hook allows you to create, update, and manipulate state inside functional components.�React has this concept of state, which are variables that hold data that our components depend on and may change over time. Whenever these variables change, React updates the UI by re-rendering the component in the DOM with the current values of the state variables.

16 of 60

useEffect hook

The Effect Hook, just like the name implies, carries out an effect each time there is a state change. By default, it runs after the first render and every time the state is updated. ��In this example, we create a state variable count with an initial value of zero. A button in the DOM will increase the value of this variable by one every time it is clicked. The useEffect hook will run every time the count variable changes and then log out some information to the console.

17 of 60

useEffect hook

The dependency array is an optional value we can pass as the second argument into our useEffect hook. ��What is in the dependency array will determine when the effect will run

18 of 60

Understanding callback functions

19 of 60

Why callback functions

A callback is just a function that's passed into another function, with the expectation that the callback will be called at the appropriate time.

Callback functions are mostly used in areas where the top level “higher order” function is doing an operation that takes an uncertain amount of time so by passing in a callback we can ensure that it is only called once the higher order functions finishes it’s thing

20 of 60

Let’s build pleb wallet in React

21 of 60

Add in the HTML for App.js

import React from "react";

import "./App.css";

​

function App() {

return (

<div className="App">

<header>

<h1>pleb wallet</h1>

</header>

{/* <Buttons /> */}

<div className="row">

<div className="balance-card">

<h2>Balance</h2>

{/* <p>{balance} sats</p> */}

</div>

<div className="balance-card">

<h2>Price</h2>

{/* <p>${price}</p> */}

</div>

</div>

<div className="row">

<div className="row-item">

{/* <Transactions transactions={transactions} /> */}

</div>

<div className="row-item">{/* <Chart chartData={chartData} /> */}</div>

</div>

<footer>

<p>Made by plebs, for plebs.</p>

</footer>

</div>

);

}

​

export default App;

22 of 60

Add in the CSS for App.css

body {

background-color: #192734;

font-family: monospace;

}

​

header {

border-bottom: 2px solid #ffbf46;

}

​

h1 {

text-align: center;

color: #8a4fff;

}

​

img {

width: 100%;

height: 100%;

}

​

footer {

border-top: 2px solid #ffbf46;

padding: 1%;

text-align: center;

position: fixed;

bottom: 0;

left: 0;

right: 0;

}

​

footer p {

color: #8a4fff;

}

​

.balance-card {

background-color: #ffbf46;

border: 2px solid #8a4fff;

padding: 1%;

width: 25%;

margin-top: 3%;

margin-bottom: 1%;

border-radius: 5px;

}

​

.balance-card p {

font-size: 1.2rem;

font-weight: bold;

}

​

.row {

width: 100%;

display: flex;

flex-direction: row;

justify-content: space-between;

}

​

.row-item {

background-color: ghostwhite;

border: 2px solid #8a4fff;

border-radius: 5px;

height: 300px;

width: 40%;

overflow: scroll;

}

​

.row-item h3 {

padding-left: 2%;

}

​

.row-item p {

padding-left: 2%;

}

​

.transaction .transaction-amount {

padding-top: 1%;

}

​

.transaction-amount {

border-bottom: 1px solid black;

}

​

@media (max-width: 876px) {

.row-item {

width: 100%;

height: 200px;

}

​

.balance-card {

width: 100%;

text-align: center;

margin-right: 2%;

margin-left: 2%;

}

}

​

@media (max-width: 615px) {

.row {

flex-direction: column;

}

​

.row-item {

margin-top: 1%;

}

​

.balance-card {

width: 80%;

margin: 0 auto;

margin-top: 1%;

}

}

​

​

23 of 60

Add the getPrice function in App.js

import React, { useEffect, useState } from "react";

import axios from "axios";

import "./App.css";

​

function App() {

const [price, setPrice] = useState(null);

​

const getPrice = () => {

// Axios is a library that makes it easy to make http requests

// After we make a request, we can use the .then() method to handle the response asynchronously

// This is an alternative to using async/await

axios

.get("https://api.coinbase.com/v2/prices/BTC-USD/spot")

// .then is a promise that will run when the API call is successful

.then((res) => {

setPrice(res.data.data.amount);

})

// .catch is a promise that will run if the API call fails

.catch((err) => {

console.log(err);

});

};

​

// useEffect is a 'hook' or special function that will run code based on a trigger

// The brackets hold the trigger that determines when the code inside of useEffect will run

// Since it is empty [] that means this code will run once on page load

useEffect(() => {

getPrice();

}, []);

​

24 of 60

Uncomment the <p> tag with price in it

<div className="balance-card">

<h2>Price</h2>

{/* <p>${price}</p> */}

</div>

​

​

​

<div className="balance-card">

<h2>Price</h2>

<p>${price}</p>

</div>

​

25 of 60

Setup useEffect to get price every 5 seconds

useEffect(() => {

// setInterval will run whatever is in the callback function every 5 seconds

const interval = setInterval(() => {

getPrice();

}, 5000);

return () => clearInterval(interval);

}, []);

​

26 of 60

Create a function to get our wallet balance

const [balance, setBalance] = useState(null);

​

​

const getWalletBalance = () => {

// ToDo: Lookup how to move the X-API-Key to a .env file to keep it secret for when we push to Github

const headers = {

"X-Api-Key": "52cac212fc664da393ac45df991fdb84",

};

axios

.get("https://legend.lnbits.com/api/v1/wallet", { headers })

.then((res) => {

// Divide our balance by 1000 since it is denominated in millisats

setBalance(res.data.balance / 1000);

})

.catch((err) => console.log(err));

};

​

​

27 of 60

Add this function into our useEffect hooks

useEffect(() => {

getPrice();

getWalletBalance();

}, []);

​

// Run these functions every 5 seconds after initial page load

useEffect(() => {

const interval = setInterval(() => {

getPrice();

getWalletBalance();

}, 5000);

return () => clearInterval(interval);

}, []);

28 of 60

Uncomment the <p> tag with balance in it

<div className="balance-card">

<h2>Balance</h2>

{/* <p>{balance} sats</p> */}

</div>

​

​

<div className="balance-card">

<h2>Balance</h2>

<p>{balance} sats</p>

</div>

​

29 of 60

Create a components folder and add the Transactions JS and CSS files in it

30 of 60

Create Transactions.js with hardcoded data

import React from "react";

import "./Transactions.css";

​

export const Transactions = () => {

const transactions = [

{

checking_id: 1,

amount: 1000,

time: 1669064951,

bolt11: "lnbc20n1p3h24fhsp590ccjwy...",

},

{

checking_id: 2,

amount: 2000,

time: 1668035952,

bolt11: "lnbc980n1p3h24fhsp5cpcjdw...",

},

];

​

// ToDo: Improve tx parsing to display internal payments, incomplete payments, and further verify the transactions we are listing out

const parseTx = (tx) => {

// turn unix timestamp into a date

// Todo: format date further to include hours, minutes, and seconds

const date = new Date(tx.time * 1000);

const formattedDate = date.toLocaleDateString("en-US");

​

return (

<div key={tx.checking_id} className="tx-item">

<p>Received from {tx.bolt11.substring(0, 25)}...</p>

<p>+{tx.amount / 1000} sats</p>

<p className="transaction-date">{formattedDate}</p>

</div>

);

};

​

return (

<div>

<h3>Transactions</h3>

{transactions.map((transaction) => {

return parseTx(transaction);

})}

</div>

);

};

​

export default Transactions;

​

31 of 60

Add the styles in Transactions.css

h3 {

padding-left: 2%;

}

​

p {

padding-left: 2%;

padding-top: 1%;

}

​

.transaction-date {

border-bottom: 1px solid black;

}

32 of 60

Import / uncomment the Transactions component in App.js (and remove the “transactions” prop for right now)

import Transactions from "./components/Transactions";

TOP of our file

​

​

​

<div className="row-item">

{/* <Transactions transactions={transactions} /> */}

</div>

​

​

​

<div className="row-item">

<Transactions />

</div>

​

​

33 of 60

Create a function to get our real transactions

const [transactions, setTransactions] = useState([]);

​

const getTransactions = () => {

// ToDo: Lookup how to move the X-API-Key to a .env file to keep it secret for when we push to Github

const headers = {

"X-Api-Key": "52cac212fc664da393ac45df991fdb84",

};

axios

.get("https://legend.lnbits.com/api/v1/payments", { headers })

.then((res) => {

setTransactions(res.data);

})

.catch((err) => console.log(err));

};

​

34 of 60

Add getTransactions to our useEffects

useEffect(() => {

getPrice();

getWalletBalance();

getTransactions();

}, []);

​

// Run these functions every 5 seconds after initial page load

useEffect(() => {

// setInterval will run whatever is in the callback function every 5 seconds

const interval = setInterval(() => {

getPrice();

getWalletBalance();

getTransactions();

}, 5000);

return () => clearInterval(interval);

}, []);

​

35 of 60

Pass our transactions array as a prop into our Transactions component

<div className="row-item">

<Transactions transactions={transactions} />

</div>

In App.js return

​

In Transactions.js

export const Transactions = ({transactions}) => {

// const transactions = [

// {

// checking_id: 1,

// amount: 1000,

// time: 1669064951,

// bolt11: "lnbc20n1p3h24fhsp590ccjwy...",

// },

// {

// checking_id: 2,

// amount: 2000,

// time: 1668035952,

// bolt11: "lnbc980n1p3h24fhsp5cpcjdw...",

// },

// ];

36 of 60

Complete our parseTx function

// ToDo: Improve tx parsing to display internal payments, incomplete payments, and further verify the transactions we are listing out

const parseTx = (tx) => {

// turn unix timestamp into a date

// Todo: format date further to include hours, minutes, and seconds

const date = new Date(tx.time * 1000);

const formattedDate = date.toLocaleDateString("en-US");

// ToDo: Handle pending payments since we are currently ignoring them and not displaying them on our past transactions list

if (tx.pending) return null;

​

if (tx.amount > 0) {

return (

<div key={tx.checking_id} className="tx-item">

<p>Received from {tx.bolt11.substring(0, 25)}...</p>

<p>+{tx.amount / 1000} sats</p>

<p className="transaction-date">{formattedDate}</p>

</div>

);

}

​

if (tx.amount < 0) {

return (

<div id={tx.checking_id} key={tx.checking_id} className="tx-item">

<p>Sent with {tx.bolt11.substring(0, 25)}...</p>

<p className="tx-amount">{tx.amount / 1000} sats</p>

<p className="transaction-date">{formattedDate}</p>

</div>

);

}

};

​

37 of 60

Add “Buttons.js” and “Buttons.css” files into your components folder

38 of 60

Add in the basic Buttons component layout in Buttons.js

import React, { useState } from "react";

import "./Buttons.css";

​

export const Buttons = () => {

return (

<div>

<div className="buttons">

<button

className="button"

onClick={() =>

console.log("This button will open a modal to send a payment")

}

>

Send

</button>

<button

className="button"

onClick={() =>

console.log("This button will open a modal to receive a payment")

}

>

Receive

</button>

</div>

{/* <PaymentsModal modalState={modalState} setModalState={setModalState} /> */}

</div>

);

};

​

export default Buttons;

​

​

39 of 60

Add in the CSS for Buttons.css

label {

padding-bottom: 2%;

}

​

.buttons {

width: 50%;

margin: 0 auto;

margin-top: 3%;

display: flex;

justify-content: space-around;

}

​

.button {

background-color: #ffbf46;

border: 2px solid #8a4fff;

border-radius: 5px;

padding: 5px;

font-size: 1.2rem;

font-family: monospace;

font-weight: bold;

width: 100px;

}

​

.buttons button:hover {

cursor: pointer;

opacity: 0.6;

}

​

​

40 of 60

Import the Buttons component into App.js and uncomment it in the return

import Buttons from "./components/Buttons";

​

<div className="App">

<header>

<h1>pleb wallet</h1>

</header>

{/* <Buttons /> */}

<div className="row">

​

​

​

<div className="App">

<header>

<h1>pleb wallet</h1>

</header>

<Buttons />

<div className="row">

​

41 of 60

Install the modal library we will be using from NPM

​

  • Stop your app from running (Ctrl+C)

​

  • Run “npm install react-modal --save”

​

  • Now start your app again with “npm start”

42 of 60

Create PaymentsModal.js and PaymentsModal.css files

43 of 60

Add in the basic PaymentsModal component

import React, { useState } from "react";

import Modal from "react-modal";

import axios from "axios";

import "./PaymentsModal.css";

​

const customStyles = {

content: {

top: "20%",

left: "40%",

right: "40%",

bottom: "auto",

},

};

​

const PaymentsModal = ({ modalState, setModalState }) => {

return (

<Modal

isOpen={modalState.open}

style={customStyles}

contentLabel="Example Modal"

appElement={document.getElementById("root")}

>

<p

className="close-button"

onClick={() => {

setModalState({ open: false, type: null });

}}

>

X

</p>

<p>Here's our modal!</p>

</Modal>

);

};

​

export default PaymentsModal;

44 of 60

Add in the styles for PaymentsModal.css

form {

margin: 2% auto;

display: flex;

flex-direction: column;

text-align: center;

}

​

form {

width: 100%;

}

​

form .button {

margin: 10% auto;

}

​

section h3 {

text-align: center;

}

​

section p {

word-wrap: break-word;

}

​

.close-button {

cursor: pointer;

text-align: end;

}

​

​

45 of 60

Add in a useState hook for the modal state and uncomment the PaymentsModal component passing in the state and setter

import PaymentsModal from "./PaymentsModal";

​

...

​

const [modalState, setModalState] = useState({

type: "",

open: false,

});

​

...

​

</div>

{/* <PaymentsModal modalState={modalState} setModalState={setModalState} /> */}

</div>

​

</div>

<PaymentsModal modalState={modalState} setModalState={setModalState} />

</div>

​

46 of 60

Add in the state for our PaymentsModal and some placeholder functions for sending/receiving

// Our state for the info we will send to either generate a new invoice or pay an invoice

const [formData, setFormData] = useState({

amount: 0,

invoiceToPay: "",

});

// Our state for storing the invoice we created to be paid

const [invoice, setInvoice] = useState("");

// Our state for the invoice we paid

const [paymentInfo, setPaymentInfo] = useState({

paymentHash: "",

checkingId: "",

});

​

const handleSend = () => {};

​

const handleReceive = () => {};

47 of 60

Add in the forms/logic for our modal

<Modal

isOpen={modalState.open}

style={customStyles}

contentLabel="Example Modal"

appElement={document.getElementById("root")}

>

<p

className="close-button"

onClick={() => {

setModalState({ open: false, type: null });

}}

>

X

</p>

{/* If it is a send */}

{modalState.type === "send" && (

<form>

<label>paste an invoice</label>

<input

type="text"

value={formData.invoiceToPay}

onChange={(e) =>

setFormData({ ...formData, invoiceToPay: e.target.value })

}

/>

<button className="button" onClick={(e) => handleSend(e)}>

Submit

</button>

</form>

)}

{/* If it is a receive */}

{modalState.type === "receive" && (

<form>

<label>enter amount</label>

<input

type="number"

min="0"

value={formData.amount}

onChange={(e) =>

setFormData({ ...formData, amount: e.target.value })

}

/>

<button className="button" onClick={(e) => handleReceive(e)}>

Submit

</button>

</form>

)}

{/* If we are displaying our newly created invoice */}

{invoice && (

<section>

<h3>Invoice created</h3>

<p>{invoice}</p>

{/* ToDo: Create a QR code out of this invoice as well */}

</section>

)}

{/* If we are displaying the status of our successful payment */}

{paymentInfo.paymentHash && (

<section>

<h3>Payment sent</h3>

<p>Payment hash: {paymentInfo.paymentHash}</p>

<p>Checking id: {paymentInfo.checkingId}</p>

</section>

)}

</Modal>

​

48 of 60

Fill in the handleSend function

const handleSend = (e) => {

// Keep the page from refreshing when the form is submitted

e.preventDefault();

​

const headers = {

"X-Api-Key": "52cac212fc664da393ac45df991fdb84",

};

const data = {

bolt11: formData.invoiceToPay,

out: true,

};

axios

.post("https://legend.lnbits.com/api/v1/payments", data, { headers })

.then((res) =>

setPaymentInfo({

paymentHash: res.data.payment_hash,

checkingId: res.data.checking_id,

})

)

.catch((err) => console.log(err));

​

return;

};

​

49 of 60

Fill in the handleReceive function

const handleReceive = (e) => {

// Keep the page from refreshing when the form is submitted

e.preventDefault();

​

const headers = {

"X-Api-Key": "52cac212fc664da393ac45df991fdb84",

};

const data = {

amount: formData.amount,

out: false,

// ToDo: Add additional form for user to be able to customize the memo

memo: "LNBits",

};

axios

.post("https://legend.lnbits.com/api/v1/payments", data, { headers })

.then((res) => setInvoice(res.data.payment_request))

.catch((err) => console.log(err));

​

return;

};

50 of 60

Add a function to clear the formState

// Function to clear all of our state when we close the modal

const clearForms = () => {

setModalState({

type: "",

open: false,

});

setInvoice("");

setPaymentInfo({

paymentHash: "",

checkingId: "",

});

setFormData({

amount: 0,

invoiceToPay: "",

});

};

...

<p

className="close-button"

onClick={() => {

clearForms();

}}

>

X

</p>

​

51 of 60

Create Chart.js and Chart.css files

52 of 60

Install the chart library we will be using from NPM

​

  • Stop your running app with (Ctrl+C)

​

  • Install our new package with “npm install react-linechart --save"

​

  • Start your app again with “npm start”

53 of 60

Add in the chart component

import React from "react";

import LineChart from "react-linechart";

import "../../node_modules/react-linechart/dist/styles.css";

import "./Chart.css";

​

const Chart = ({ chartData }) => {

if (!chartData && !chartData?.length) return null;

​

const data = [

{

color: "steelblue",

points: chartData,

},

];

​

return (

<div className="chart-container">

{chartData && chartData.length <= 1 ? (

// ToDo: Add a loading spinner

<p>Loading</p>

) : (

<LineChart

xLabel="Time"

height={300}

// ToDo: Customize width to be responsive based on screen size

width={550}

data={data}

onPointHover={(obj) => `price: $${obj.y}<br />time: ${obj.x}`}

ticks={4}

hideYLabel={true}

hideXLabel={true}

xDisplay={(timestamp) =>

new Date(timestamp).toLocaleTimeString("en-US")

}

/>

)}

</div>

);

};

​

export default Chart;

​

54 of 60

Add in our 1 style in Chart.css

.chart-container {

padding-left: 1%;

}

55 of 60

Add updateChartData function and chartData state into App.js

const [chartData, setChartData] = useState(null);

​

​

const updateChartData = (currentPrice) => {

const timestamp = Date.now();

// We are able to grab the previous state to look at it and do logic before adding new data to it

setChartData((prevState) => {

// If we have no previous state, create a new array with the new price data

if (!prevState)

return [

{

x: timestamp,

y: Number(currentPrice),

},

];

// If the timestamp or price has not changed, we don't want to add a new point

if (

prevState[prevState.length - 1].x === timestamp ||

prevState[prevState.length - 1].y === Number(currentPrice)

)

return prevState;

// If we have previous state than keep it and add the new price data to the end of the array

return [

// Here we use the "spread operator" to copy the previous state

...prevState,

{

x: timestamp,

y: Number(currentPrice),

},

];

});

};

56 of 60

Pass each updated price into setChartData from the getPrice function

const getPrice = () => {

// Axios is a library that makes it easy to make http requests

// After we make a request, we can use the .then() method to handle the response asynchronously

// This is an alternative to using async/await

axios

.get("https://api.coinbase.com/v2/prices/BTC-USD/spot")

// .then is a promise that will run when the API call is successful

.then((res) => {

setPrice(res.data.data.amount);

updateChartData(res.data.data.amount);

})

// .catch is a promise that will run if the API call fails

.catch((err) => {

console.log(err);

});

};

​

57 of 60

Import the chart component in App.js and uncomment it in the return

import Chart from "./components/Chart";

​

​

...

​

<div className="row-item">{/* <Chart chartData={chartData} /> */}</div>

​

​

​

​

<div className="row-item">

<Chart chartData={chartData} />

</div>

​

58 of 60

Pleb wallet should now be working!

Push your work to Github now so vercel can update and deploy our new build

59 of 60

React resources

60 of 60

Get in contact with me

Twitter @bitcoinplebdev