

NFT Project Series Part 7: Building Our Frontend Using Remix
source link: https://blog.geekyants.com/nft-project-series-part-7-building-our-frontend-using-remix-249f0fdb6a76
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

NFT Project Series Part 7: Building Our Frontend Using Remix
Learn how to create a frontend for Web 2.0 app using Remix

TABLE OF CONTENTS
- What Will We Build?
- Installation And Clean Initial Setup
- Testing Our App
- Final Words
In the last part, we looked into how to build the frontend for our Web 2.0 backend in React. In this one, we will build the same in Remix. The prerequisite here is to know about Remix basics. So, let’s start!
What Will We Build?
First things first, before we begin coding, we need to know how will our app look at the end of this tutorial? Here’s how:
When the required wallet is not installed:
When the required wallet is installed and not connected:
When the required wallet is installed and connected:
Keyboard Form Page:
Installation And Clean Initial Setup
Now, let’s install our react-app:
npx create-remix@latest
This will ask a bunch of questions so type as shown in the image below:
and then:
cd remix-run && code .
Now, at this stage, if you followed the last article, you know we have assets
folder in our next app. I want you to copy that folder and paste it in our public
folder in our app. Or you can directly download it from here
. Then, inside our assets
folder, create style.css
if it doesn't exists, and paste the following code (replace the existing one if it exists):
@import url('https://fonts.googleapis.com/css2?family=Work+Sans:wght@400;600&display=swap');*,
::after,
::before {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Work Sans', sans-serif;
}html {
font-size: 100%;
}body {
color: #121212;
background-color: #ffffff;
padding: 2rem;
}.home {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
margin: 0;
padding: 0;
}.btn {
outline: none;
border: none;
padding: 1rem;
font-size: 1.2rem;
border-radius: 0.2rem;
cursor: pointer;
}.btn-tip {
padding: 0.7rem;
font-size: 0.8rem;
font-weight: 600;
background: #0077ff;
color: white;
margin-top: 1rem;
width: 100px;
}.btn-no-tip {
padding: 0.7rem;
font-size: 0.8rem;
font-weight: 600;
background: #008b1f;
color: white;
margin-top: 1rem;
width: 100px;
}.btn-black {
background: #121212;
color: white;
}.btn-black:hover,
.btn-black:active,
.btn-black:focus {
background: #121212de;
}.btn-link {
text-decoration: none;
display: inline-block;
}.heading {
margin-bottom: 2rem;
}.nfts {
margin-top: 2rem;
display: grid;
grid-template-columns: auto;
}@media (min-width: 760px) {
.nfts {
grid-template-columns: auto auto auto;
gap: 1rem;
}
}.preview {
margin-top: 2rem;
display: flex;
align-items: center;
width: 80%;
margin: 2rem auto;
max-width: 600px;
}.borders {
border: 1px solid #ddd;
margin-top: 1rem;
border-radius: 0.2rem;
padding: 0.5rem;
}.none {
filter: none;
}.sepia {
filter: sepia(100%);
}.grayscale {
filter: grayscale(100%);
}.invert {
filter: invert(100%);
}.hue-rotate-90 {
filter: hue-rotate(90deg);
}.hue-rotate-180deg {
filter: hue-rotate(180deg);
}.form {
width: 80%;
margin: auto;
max-width: 600px;
}.form-group {
display: flex;
flex-direction: column;
margin-bottom: 1rem;
gap: 0.4rem;
}.form-group label {
font-weight: bold;
}.form-group select {
font-size: 1.2rem;
padding: 0.6rem 0.2rem;
cursor: pointer;
}
Once this is done, let’s go inside our root.jsx
file inside app
folder and write the following code:
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from 'remix';import styles from 'public/assets/style.css';export function meta() {
return {
charset: 'utf-8',
title: 'Keyboard NFT Minters',
viewport: 'width=device-width,initial-scale=1',
};
}export function links() {
return [{ rel: 'stylesheet', href: styles }];
}export default function App() {
return (
<html lang="en">
<head>
<Meta />
<Links />
</head>
<body>
<Outlet />
<ScrollRestoration />
<Scripts />
{process.env.NODE_ENV === 'development' && <LiveReload />}
</body>
</html>
);
}
Here, we imported our global style file:
import styles from 'public/assets/style.css';export function links() {
return [{ rel: 'stylesheet', href: styles }];
}
We then changed our app title to Keyboard NFT Minters
and made sure to wrap our <LiveReload />
inside environment based logic: {process.env.NODE_ENV === 'development' && <LiveReload />}
.
We don’t have to touch entry.client.jsx
and entry.server.jsx
.
In Remix, the route pages go inside the routes
folder. So, in our routes
folder, delete everything that is inside index.jsx
file and replace it with:
import { useEffect, useState } from 'react';
import { useLoaderData } from 'remix';
import axios from 'axios';
import Keyboard from '~/components/Keyboard';const API_URL = 'http://localhost:5000';export async function loader() {
const { data } = await axios.get(`${API_URL}/nft`);
if (data.success) {
return data.data;
}
return [];
}export default function Index() {
const [mounted, setMounted] = useState(false);
const [isEthereum, setIsEthereum] = useState(false);
const [isConnected, setIsConnected] = useState(false);
const keyboardNFTs = useLoaderData(); useEffect(() => setMounted(true), []); useEffect(() => {
if (
typeof window !== 'undefined' &&
typeof window.ethereum !== 'undefined'
) {
setIsEthereum(true);
if (localStorage.getItem('metamask')) {
setIsConnected(true);
}
}
}, []); async function connectMetamask() {
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts',
});
const account = accounts[0];
if (typeof account === 'string') {
localStorage.setItem('metamask', account);
setIsConnected(true);
}
} if (mounted && !isEthereum) {
return (
<main className="home">
<h1 className="heading">Keyboard NFT Minter</h1>
<a
href="https://metamask.io"
className="btn btn-black btn-link"
target="_blank"
rel="noreferrer"
>
Please install metamask wallet to use this app
</a>
</main>
);
} if (mounted && isEthereum && !isConnected) {
return (
<main className="home">
<h1 className="heading">Keyboard NFT Minter</h1>
<button className="btn btn-black" onClick={connectMetamask}>
Connect with Metamask
</button>
</main>
);
} if (mounted && isEthereum && isConnected) {
return (
<main className="home">
<h1 className="heading">Keyboard NFT Minter</h1>
<a className="btn btn-black btn-link" href="/create-nft">
Create new NFT
</a>
<section className="nfts">
{keyboardNFTs.map((keyboard) => {
return (
<Keyboard key={keyboard.id} keyboard={keyboard} preview={false} />
);
})}
</section>
</main>
);
} return null;
}
This here is our Home page
. This needs code block wise explanation, so let me explain:
export async function loader() {
const { data } = await axios.get(`${API_URL}/nft`);
if (data.success) {
return data.data;
}
return [];
}
Here, we are loading our keyboards from our database on the server side. This loader()
function can be used with useLoaderData()
to get the data in Remix as we do in line:
const keyboardNFTs = useLoaderData();
We are also using an additional state variable named mounted
to indicate that our component has mounted:
const [mounted, setMounted] = useState(false);useEffect(() => setMounted(true), []);
This stops the error while accessing window
object in our app. Try removing it, you should see some error. We then took 2 states using react-hook useState()
. Two boolean states define the UI State of our App namely:
- No metamask wallet is installed or wallet is not connected:
(isEthereum = false OR isConnected = false)
. - Wallet is connected:
(isConnected = true AND isEthereum = true)
.
The keyboardNFTs
state holds the keyboard list we get from the database after request through axios
. We get our list using axios in loader()
function and then return the data directly. This makes sure that we get our keyboards from our database.
We also are checking for ethereum
object availability inside useEffect
hook as (typeof window !== 'undefined' && typeof window.ethereum !== 'undefined')
and based on that, we are setting the UI state boolean variable isEthereum
which indicates whether metamask is installed or not.
We also have connectMetamask()
function which signs in our users using metamask and stores their address in localStorage and sets the boolean isConnected
to true.
Finally, we render the UI based on our two UI State boolean values to complete our Home page. Link
is used to route to different page without page refresh. We will notice an error in Keyboard Component
at this stage as it doesn't exists.
So, next we go create our Keyboard.jsx
file inside our components
folder in our app
folder and populate it with the following code:
import { useEffect, useState } from 'react';export default function Keyboard({ keyboard, preview }) {
const [alt, setAlt] = useState('');
const [imagePath, setImagePath] = useState('');
const [style, setStyle] = useState('');
const [connectedAccount, setConnectedAccount] = useState('');
const [isOwner, setIsOwner] = useState(false); useEffect(() => {
displayImage();
}, [keyboard]); useEffect(() => {
if (localStorage.getItem('metamask')) {
setConnectedAccount(localStorage.getItem('metamask')); if (connectedAccount === keyboard.ownerAddress) {
setIsOwner(true);
}
}
}, [connectedAccount, keyboard.ownerAddress]); function getKindDir(kind) {
return {
0: 'sixty-percent',
1: 'seventy-five-percent',
2: 'eighty-percent',
3: 'iso-105',
}[kind];
} function displayImage() {
const kindDir = getKindDir(keyboard.keyboardKind);
const filename = keyboard.keyboardType.toUpperCase();
setImagePath(`assets/keyboards/${kindDir}/${filename}.png`);
setAlt(
`${kindDir} keyboard with ${filename} keys ${
keyboard.keyboardFilter
? `with
${keyboard.keyboardFilter}`
: ''
}`
);
setStyle(keyboard.keyboardFilter);
} return (
<div className="nft">
{preview && <h2>Preview</h2>}
<div className="borders">
<img
height={230}
width={360}
className={style}
src={imagePath}
alt={alt}
/>
</div>
{!preview && !isOwner && <button className="btn btn-tip">Tip</button>}
{!preview && isOwner && (
<button className="btn btn-no-tip">You own it!</button>
)}
</div>
);
}
Here, we have two props in our component: keyboard
and preview
. The preview
is a boolean prop which is used to display the Preview
heading. The keyboard
is an object prop containing the keyboard data. Notice that we pass this keyboard as a dependency in our useEffect
hook responsible for displaying the keyboard. This is because when we change the form values while selecting to see the preview, the keyboard values changes and therefore we need it inside the dependency to detect the change and run the displayImage()
function again.
getKindDir()
function here takes the kind
property value as in 0, 1, 2, and 3 and returns the directory name of the keyboard (under assets
folder). Filename
comes from the type
property.
We are also using a boolean isOwner
to know if the current address belongs to the keyboard creator's address. For this, we are using useEffect
hook and the following line:
useEffect(() => {
if (localStorage.getItem('metamask')) {
setConnectedAccount(localStorage.getItem('metamask')); if (connectedAccount === keyboard.ownerAddress) {
setIsOwner(true);
}
}
}, [connectedAccount, keyboard.ownerAddress]);
Here, this is dependent on connectedAccount
and ownerAddress
property from keyboard.
Finally, we go create our create-nft.jsx
file inside routes
folder and write the following code:
import { useEffect, useState } from 'react';
import axios from 'axios';
import Keyboard from '~/components/Keyboard';const API_URL = 'http://localhost:5000';export default function CreateNFT() {
const [keyboard, setKeyboard] = useState({
keyboardKind: 0,
keyboardType: 'pbt',
keyboardFilter: 'none',
ownerAddress: '',
}); useEffect(() => {
setKeyboard({
...keyboard,
ownerAddress: localStorage.getItem('metamask'),
});
}, []); function change(event) {
setKeyboard({ ...keyboard, [event.target.name]: event.target.value });
} async function onSubmit(event) {
event.preventDefault();
const nft = {
kind: keyboard.keyboardKind,
type: keyboard.keyboardType,
filter: keyboard.keyboardFilter,
owner: keyboard.ownerAddress,
};
const { data } = await axios.post(`${API_URL}/nft`, nft);
if (data.success) {
notify(data.message);
window.location.href = '/';
}
} function notify(message) {
alert(message);
} return (
<>
<form className="form" onSubmit={onSubmit}>
<div className="form-group">
<label htmlFor="kind">Keyboard Kind</label>
<select
id="kind"
name="keyboardKind"
onChange={change}
defaultValue={keyboard.keyboardKind}
>
<option value="0">60%</option>
<option value="1">75%</option>
<option value="2">80%</option>
<option value="3">ISO-105</option>
</select>
</div>
<div className="form-group">
<label htmlFor="type">Keyboard Type</label>
<select
id="type"
name="keyboardType"
onChange={change}
defaultValue={keyboard.keyboardType}
>
<option value="abs">ABS</option>
<option value="pbt">PBT</option>
</select>
</div>
<div className="form-group">
<label htmlFor="filter">Keyboard Filter</label>
<select
id="filter"
name="keyboardFilter"
onChange={change}
defaultValue={keyboard.keyboardFilter}
>
<option value="none">None</option>
<option value="sepia">Sepia</option>
<option value="grayscale">Grayscale</option>
<option value="invert">Invert</option>
<option value="hue-rotate-90">Hue Rotate (90°)</option>
<option value="hue-rotate-180">Hue Rotate (180°)</option>
</select>
</div>
<button type="submit" className="btn btn-black">
Mint NFT
</button>
</form> <section className="preview">
<Keyboard preview={true} keyboard={keyboard} />
</section>
</>
);
}
This needs a little in-depth breakdown in some important places, so:
function change(event) {
setKeyboard({ ...keyboard, [event.target.name]: event.target.value });
}
Here, we are declaring only one function change()
which takes care of changing the individual property values of keyboard object. The trick is to use name
input html property as in [event.target.name]
to differentiate between the keyboard object keys. This means that the name values must be the same as object keys in keyboard: name="keyboardKind"
, name="keyboardType"
, and name="keyboardFilter"
.
We are then submitting the values to our backend as in:
async function onSubmit(event) {
event.preventDefault();
const nft = {
kind: keyboard.keyboardKind,
type: keyboard.keyboardType,
filter: keyboard.keyboardFilter,
owner: keyboard.ownerAddress,
};
console.log(nft);
const { data } = await axios.post(`${API_URL}/nft`, nft);
if (data.success) {
notify(data.message);
window.location.href = '/';
}
} function notify(message) {
alert(message);
}
Notice that we used axios
to do post request to our backend we build in Part 3 of the series. This means the backend must be up and running while making this request. Also, rather than doing alert
directly, we use notify
instead. This allows us to safely replace alert in future with toastify notification coded outside of this UI component (to increase re-usability by decoupling). In case you want to know this technique in depth (on conceptual level), watch this video I did on my channel a while back: Birthday Palindrome in React and JavaScript [USE SAME CODE!]
We also forgot to install axios
in our setup so let's do it now:
npm install axios
And that should do the trick!
Testing Our App
At this point, we have successfully coded our app. It’s time to test it out. So let’s run the command:
npm run dev
This opens our app. Now test it out. We should be able to create a new keyboard and see the difference between the ones we created and the ones created by someone else. To test this, all we need is to run our app in 2 different browsers with 2 different metamask addresses.
Final Words
This was one big read! Congratulations! You made it till the end in this short-attention-span modern world. Tell me if you liked it or not. In the next article, we will finally start building our Solidity Smart Contract. See you then. Bye!
Recommend
-
6
Building a Survey Site Using Remix Framework and KendoReact In this post, we will be creating a survey website with the help of Remix framewo...
-
8
NFT Project Series Part 5: Building Frontend Using ReactLearn how to create a frontend for Web 2.0 app using React
-
15
NFT Project Series Part 6: Building Our Frontend Through Next.jsLearn how to create a frontend for Web 2.0 app using Next.js
-
7
NFT Project Series Part 4: Building Our Frontend Through AngularLearn how to create a frontend for Web 2.0 app using Angular
-
12
NFT Project Series Part 12: Modifying our Remix FrontendLearn How to Integrate Solidity Smart Contract with RemixRun
-
6
NFT Project Series Part 9: Modifying our Angular FrontendLearn How to Integrate Solidity Smart Contract with Angular
-
4
NFT Project Series Part 10: Modifying our React FrontendLearn How to Integrate Solidity Smart Contract with React
-
9
NFT Project Series Part 11: Modifying our Next.js FrontendLearn How to Integrate Solidity Smart Contract with Next.js
-
9
1. Shopify is acquiring Remix! 🛍 To say that there has been a lot going on in the Server Side Rendering area in recent weeks is like saying nothing at all. Less than two weeks ago,
-
5
Flagship sailing upstream (is this anything) — Asahi Linux’s new “flagship” distro for M-series Macs is a Fedora Remix An "upstream-first" effort on a more familiar platfor...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK