NEAR Blockchain
Video/Text

App.js – The Control Center

Lesson 13 Chapter 3 Module 2

I said that index.js was the starting point for your Dapp.  It's like the gateway to your Dapp.  If you recall, the component it pointed to at the root directory of / is App - and App is defined in App.js.  App.js acts like a control center of sorts.  It's the framework that we build to fill with other components and interactions.  With React, we build single page applications (SPAs) - App.js sets up that overarching page and controls what is shown/when.

Open up App.js and let's take a look - starting with the imports.

import 'regenerator-runtime/runtime'
import React, { useState, useEffect } from 'react'

// Material UI imports
import LinearProgress from '@material-ui/core/LinearProgress'

// DApp component imports
import SignIn from './components/common/SignIn/signIn'
import Initialize from './components/Initialize/initialize'
import TokenData from './components/TokenData/tokenData'

// import stylesheets
import './global.css'

The first import - 'regenerator-runtime/runtime' has something to do with compilation.  Honestly, I haven't figured it out myself yet - but the research I've done suggests it does some kind of reformatting of javascript so things work as expected.  I'll return and update this as I figure it out.  For now, just leave it/ignore it.

Next up, we bring in React and a couple specific components of it - useState and useEffect.  In very brief general terms, we need React because we're building a React app - pretty self-explanatory.  We need useState/useEffect because we are using React hooks and those two components are necessary to set up and control refresh of our Dapp's state.  State and props are two concepts of React we'll cover as we come to them - both super important.

The Dapp component imports are the components we're going to build and then import here to use.

Last is a global stylesheet.  I've left this here, but as we're using Material-UI our styles are typically defined either in each component or in a global component that is imported into each component.  To keep things together, we're going to do the styling in each component.

The next line starts our component.  This is a functional component.  In React you can build functional components or class components.  With the introduction of React hooks, class components are no longer required (although you can use them if you want).  We're going to use hooks and functional components throughout.

This defines the name of our functional component (App) and marks it as exportable and the default component of this file.  This means we can import it into other files/components (like we did in index.js) and when we do we use an import App from... statement instead of an import { App } from ... statement.  The second is for non-default functions marked as exportable.  

We can illustrate this more with the Material-UI components, we can import a Button with import Button from '@material-ui/core/Button' because it's marked as the default function in that file.  We can also do a import { Button} from '@material-ui/core' because it's listed as an exportable component in the index file in that directory.  We're going to follow the first format with Material-UI in this project.

export default function App() {

State

Moving on down, we arrive at state setup which is important to understand.

// state setup
const [loggedIn, setLoginState] = useState(false)
const [initialized, setInit] = useState(false)
const [done, setDone] = useState(false)
const [accountId, setAccountId] = useState()
const [tokenOwner, setTokenOwner] = useState()
const [initialSupply, setInitialSupply] = useState()
const [totalSupply, setTotalSupply] = useState()
const [tokenName, setTokenName] = useState()
const [tokenSymbol, setTokenSymbol] = useState()
const [precision, setPrecision] = useState()

So what we're doing here is adding local state to this App component and because App is the control center or framework of our entire Dapp - it's kind of like global state as well.  State is an object of the parts of the Dapp that can change and we need it if we want our Dapp to do anything.  Each component in our Dapp can have it's own state.  One caveat here, we typically also put results of server calls or volatile information into state.  In the case of blockchain Dapps, instead of server calls, we put results of calls to the chain which we're going to see with the variables like tokenName, tokenSymbol, etc...

The above statements use the React hook format to define a state variable and a method to update it.  For example, with the first line - the variable name is a constant - loggedIn.  setLoginState is the name of the function we use to update that variable (you never update state variables directly).  The useState function comes from React which handles all this and we define false as the initial default value of loggedIn when the Dapp starts up.

Note that I've chosen to define each state variable/method separately.  Alternatively, we could put all of state inside of one variable and update it with one method which would be more similar to how a class component was doing things.  For instance, we could do:

// state setup
const [state, setState] = useState({
initialized: false,
done: false,
accountId: '',
etc...
})

We would then call setState() with the new state information to update it.  Note that with React hooks, state is overwritten, not merged - so if doing things this way, you need to be careful to preserve the state of things you aren't trying to change when calling setState.

I personally find it easier to comprehend what I'm changing/what's going on by separating everything out into their own variables/functions.

The state object's variables define the entire state of the Dapp at any given point in time.  Whenever one thing inside of state changes, it causes the entire Dapp to re-render (redraw itself).  This is not the same as a page refresh where everything disappears and then updated info comes back.  You won't generally see the page refresh, the components will just update which is a much better user experience.

React only knows when a state variable changes because we tell it.  That happens when we call the method associated with each variable (for example, setLoginState for the loggedIn variable).  useEffect provides some more control over when things are re-rendered - we'll get to that in a moment.

It's easy to change state in the component it's defined in.  You just call it's state change method and pass it the new variable.  It's a little more difficult if you want a child component to change it's parent's state.  To do that, we need to understand the idea of props and how to pass things down to a child component.  That brings us to the next part of App.js.

function handleInitChange(newState) {
setInit(newState)
}

This is a custom hook.  It allows us to update App's initialization state from other components, but to do so, we need to pass this function as a property (props) to the component.  If you look further ahead in App.js you'll see where we use the <Initialize> component.

<Initialize
accountId={accountId}
done={done}
handleInitChange={handleInitChange}
initialized={initialized}
/>

All of the variables defined there correspond to state variables we defined above in App.js.  They all get passed as props to the Initialize component that then allows the Initialize component to use them in it's logic which we'll see later on.  handleInitChange is the name of the custom hook we defined above and it provides a way for Initialize to call the setInit method of the state variable in App.js from the Initialize component - thus changing the state variable we defined as initialized.  That's how you make changes to state variables in parent components - pass down a hook (function) that allows you to access the state's change method.  If you don't pass a function - just a variable - you can think of it as a one-way road.  The prop representing state goes down and you can use it as is, but you can't change it and it can't come back up.

The next thing we see is the useEffect() function.  It's used with React hooks to control side-effects during render and it typically replaces the lifecycle methods we'd normally find in class components (componentDidMount, componentDidUpdate, etc...).  To fully understand this, let's look at the overall structure of the useEffect function before we dive into exactly what this implementation is actually doing in our Dapp.

useEffect(
() => {

}, []
)

We start out obviously by calling useEffect and passing it a callback function (meaning the thing inside useEffect() is going to do something.  In the first part, between the {}, we're going to define some actions we want the component to do.  We can do some conditions checks or fetch some data when the component renders for the first time.

Sometimes, we're going to want the component to re-render only when a state variable changes (more efficient that way).  That's controlled by the [].  The default behaviour with an empty [] is to render the first time and on any update.  We can make that more efficient and potentially stop some unwanted side effects by passing it an array of state variables to watch and if one changes, it will re-render the component if the new value is different than the current value.

With that, let's look at what our useEffect() function does in our fungible token dapp.  The first thing we see it doing is:

if (window.walletConnection.isSignedIn()) {
setLoginState(true)
setAccountId(window.accountId)

Pretty self-explanatory - it's calling the window.walletConnection that got initiated in the utils.js file and checking to see if the current user has signed in to their NEAR account or not.  If true, it sets the state loggedIn variable to true and the state's accountId variable to the user's account ID (e.g. vitalpointai.testnet).

The next function is a little more interesting.  Look at the first part of it:

async function fetchData() {
try {
// retrieve Token Name and set state
try {
// window.contract is set in utils.js after being called by initContract in index.js
let name = await window.contract.getTokenName()
setTokenName(name)
} catch (err) {
console.log('token name not set yet')
return false
}

async is a reserved keyword in javascript that mark this function as asynchronous.  Inside it, you'll see await which goes hand in hand with async.  Methods can happen synchronously or asynchronously.  Synchronously means it's going to run and nothing else can run at the same time meaning it can potentially block execution of your program while it waits (await) to complete its task.  Asynchronous functions will happen concurrently so they don't block execution, but you'll have to wait for them to finish before you can use whatever they are returning.  async/await is doing the same as the promise.then() structure from earlier versions of Javascript.

We defined the fetchData function because when our Dapp starts up, we want it to go look at the chain and return some information - tokenName, tokenSymbol, precision, supply and so on.  That's done, in the case of tokenName by calling the getTokenName() method we defined when we built our contract - main.ts.  The let name = await window.contract.getTokenName() is accessing the getTokenName() method on our contract and returning the current value of token name to store in name.  Then we're setting the state variable tokenName to name using setTokenName(name).

It's possible that if this is the first time this Dapp has run and no token has been created for getTokenName() to fail - not return anything.  That would produce an error which we need to handle.  When calling contract functions, it's typically a good idea to put them inside a try/catch block.  That basically says try something and if it works great, carry on.  If you try and it fails, catch the error and do this instead.  In our case here, if it fails we're outputting a message to the console saying the token name has not been set and returning false from the function saying it didn't complete.

Look at the rest of this function in App.js and notice that we're putting a bunch of try/catch statements, each pulling back a different piece of data from the chain, inside of one big try/catch block.  Basically, we want all the information of our token or we want to deal with the error.  (There shouldn't be a tokenName without a symbol for instance - honestly, that shouldn't happen given how we're initializing the token, but we should always try and handle any errors that might show up.)  If it fails, we setDone(false) meaning the data didn't load properly.

I won't cover each individual information retrieval try/catch block as they are all very similar to the first one with tokenName.  Instead, let's skip down to where we actually call the fetchData() function we just defined.  It's immediately after it.

fetchData()
.then((res) => {
res ? setInit(true) : setInit(false)
setDone(true)
})

fetchData() here is using the javascript promise format.  We call the function .then do something with the result.  The result gets put in res - so we check that res is true (meaning the fetchData function completed and returned/set state variables correctly).  If true, we indicate that the token has been initialized (setInit(true)) otherwise false.  That res ? ... : ... is a ternary statement.  Finally, we setDone(true) indicating the data all loaded successfully.  We pass done to our child components later on.

// The second argument to useEffect tells React when to re-run the effect
// it compares current value and if different - re-renders
[initialized]
)

The last part of useEffect is where we tell it to watch the initialized variable - it it changes, we want it to run everything inside again to update the state information.  In our Dapp, we won't have a way of changing the token information after it is first initialized - so this should never happen, but if it did - this would update everything.

The rest of the App.js component handles what we're going to see on the screen.  First, here's the code...

// if not signed in, return early with sign-in component
 if (!window.walletConnection.isSignedIn()) {
   return (<SignIn />)
  }

 // if not done loading all the data, show a progress bar, otherwise show the content
 if(!done) {
   return <LinearProgress />
 } else {
   if(!initialized) {
     return (
       <Initialize
         accountId={accountId}
         done={done}
         handleInitChange={handleInitChange}
         initialized={initialized}
       />
     )
   } else {
     return (
       <TokenData
         accountId={accountId}
         tokenName={tokenName}
         tokenSymbol={tokenSymbol}
         currentSupply={totalSupply}
         tokenOwner={tokenOwner}
         done={done}
         />
     )
   }
 }
}

This simply defines what child component we want rendered on screen. We still need to build these so they are not in your directory yet.

The first if statement checks to see if we're logged in.  If not, we want App.js to display the <SignIn /> component.

Next, if(!done) - meaning if we are signed in but the data is not done loading, display the <LinearProgress /> component which is a progress indicator/waiting bar.

When done is true, we move on to either display the <Initialize /> component or the <TokenData /> component depending on whether the intialized state variable is true or false (meaning we have already created the token or not).

Initialize Component

TokenData Component

As mentioned above, in some of these components (Initialize, TokenData) - we are passing down props for them to use as they do their job.  Note that these are also the same component names that we had to import earlier.

And that's the end of App.js.  Fairly easy to figure out what's going on right?

Next up, we'll build our SignIn component.

Pen
>