What are NEAR Components?

NEAR Components are a new way to build web applications. They are composable, reusable and decentralized.

img


Familiar to Web Developers

NEAR Components are built on top of React Components, meaning that they:

  • Handle input through the props variable
  • Handle state through the useState hook
  • Handle side effects through the useEffect hook

“`jsx
const name = props.name || “Maria”;
const [count, setCount] = useState(1);

return (

{count} cheers for {name}!

);
“`

In contrast with React, NEAR Components are not wrapped in a function or class definition.

Indeed, when writing a NEAR Component, you focus on writing the body of the component, which is a function that returns the JSX to be rendered.


NEAR Native

NEAR Components can readily interact with smart contracts in the NEAR Blockchain. While view methods are free to query by anyone, call methods require the user to be logged in.

“`jsx
const counter = Near.view(‘counter.near-examples.testnet’, ‘get_num’)

if(counter === null) return ‘Loading…’

const add = () => {
Near.call(‘counter.near-examples.testnet’, ‘increment’)
}

const subtract = () => {
Near.call(‘counter.near-examples.testnet’, ‘decrement’)
}

return

Counter: {counter}

{!context.accountId &&

Please login to interact with the contract

}
{context.accountId &&



>
}
>
“`


Social from the Get-Go

NEAR Components are easily integrated with NEAR Social, a social network built on NEAR.

“`js
const item = (blockHeight) => ({ type: ‘social’, path: ‘influencer.testnet/post/main’, blockHeight });

// retrieve indexed posts by influencer.testnet
const idx_posts = Social.index(
‘post’, ‘main’, { accountId: [‘influencer.testnet’] }
);

if (idx_posts === null) return ‘Loading…’;

// retrieve likes for each post
const likes = idx_posts.map(
index => Social.index(‘like’, item(index.blockHeight)).length
);

// retrieve data for each post
const post_data = idx_posts.map(
index => Social.get(`${index.accountId}/post/main`, index.blockHeight)
);

// defined “Like” function
const like = (blockHeight) => Social.set(
{index:{like: JSON.stringify({key: item(blockHeight), value: {type: ‘like’}})}}
)

return

Posts from influencer.testnet

{post_data.map((post, idx) =>

{JSON.parse(post).text} – {likes[idx]} likes

{context.accountId && }

)}
>

“`


Leveraging the cheap storage and computation of the NEAR Blockchain, NEAR Components’ code is stored fully on-chain in the SocialDB smart contract (social.near).

“`js
// retrieving the code of a stored component
return Social.get(‘influencer.testnet/widget/Greeter’)
“`

Once deployed, a component can be imported and used by any other component. Composing components as LEGO blocks allows you to build complex applications.

“`js
// Rendering the component with props
return ;
“`


Multi-Chain by Design

NEAR Components can easily interact with Ethereum compatible blockchains, helping to easily create decentralized frontends for multi-chain applications.

“`js
if (
state.chainId === undefined &&
ethers !== undefined &&
Ethers.send(“eth_requestAccounts”, [])[0]
) {
Ethers.provider()
.getNetwork()
.then((chainIdData) => {
if (chainIdData?.chainId) {
State.update({ chainId: chainIdData.chainId });
}
});
}
if (state.chainId !== undefined && state.chainId !== 1) {
return

Switch to Ethereum Mainnet

;
}

// FETCH LIDO ABI

const lidoContract = “0xae7ab96520de3a18e5e111b5eaab095312d7fe84”;
const tokenDecimals = 18;

const lidoAbi = fetch(
“https://raw.githubusercontent.com/lidofinance/lido-subgraph/master/abis/Lido.json”
);
if (!lidoAbi.ok) {
return “Loading”;
}

const iface = new ethers.utils.Interface(lidoAbi.body);

// FETCH LIDO STAKING APR

if (state.lidoArp === undefined) {
const apr = fetch(
“https://api.allorigins.win/get?url=https://stake.lido.fi/api/sma-steth-apr”
);
if (!apr) return;
State.update({ lidoArp: JSON.parse(apr?.body?.contents) ?? “…” });
}

// HELPER FUNCTIONS

const getStakedBalance = (receiver) => {
const encodedData = iface.encodeFunctionData(“balanceOf”, [receiver]);

return Ethers.provider()
.call({
to: lidoContract,
data: encodedData,
})
.then((rawBalance) => {
const receiverBalanceHex = iface.decodeFunctionResult(
“balanceOf”,
rawBalance
);

return Big(receiverBalanceHex.toString())
.div(Big(10).pow(tokenDecimals))
.toFixed(2)
.replace(/d(?=(d{3})+.)/g, “$&,”);
});
};

const submitEthers = (strEther, _referral) => {
if (!strEther) {
return console.log(“Amount is missing”);
}
const erc20 = new ethers.Contract(
lidoContract,
lidoAbi.body,
Ethers.provider().getSigner()
);

let amount = ethers.utils.parseUnits(strEther, tokenDecimals).toHexString();

erc20.submit(lidoContract, { value: amount }).then((transactionHash) => {
console.log(“transactionHash is ” + transactionHash);
});
};

// DETECT SENDER

if (state.sender === undefined) {
const accounts = Ethers.send(“eth_requestAccounts”, []);
if (accounts.length) {
State.update({ sender: accounts[0] });
console.log(“set sender”, accounts[0]);
}
}

//if (!state.sender) return “Please login first”;

// FETCH SENDER BALANCE

if (state.balance === undefined && state.sender) {
Ethers.provider()
.getBalance(state.sender)
.then((balance) => {
State.update({ balance: Big(balance).div(Big(10).pow(18)).toFixed(2) });
});
}

// FETCH SENDER STETH BALANCE

if (state.stakedBalance === undefined && state.sender) {
getStakedBalance(state.sender).then((stakedBalance) => {
State.update({ stakedBalance });
});
}

// FETCH TX COST

if (state.txCost === undefined) {
const gasEstimate = ethers.BigNumber.from(1875000);
const gasPrice = ethers.BigNumber.from(1500000000);

const gasCostInWei = gasEstimate.mul(gasPrice);
const gasCostInEth = ethers.utils.formatEther(gasCostInWei);

let responseGql = fetch(
“https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2”,
{
method: “POST”,
headers: { “Content-Type”: “application/json” },
body: JSON.stringify({
query: `{
bundle( ethPrice
}
}`,
}),
}
);

if (!responseGql) return “”;

const ethPriceInUsd = responseGql.body.data.bundle.ethPrice;

const txCost = Number(gasCostInEth) * Number(ethPriceInUsd);

State.update({ txCost: `$${txCost.toFixed(2)}` });
}

// FETCH CSS

const cssFont = fetch(
“https://fonts.googleapis.com/css2?family=Manrope:wght@200;300;400;500;600;700;800”
).body;
const css = fetch(
“https://pluminite.mypinata.cloud/ipfs/Qmboz8aoSvVXLeP5pZbRtNKtDD3kX5D9DEnfMn2ZGSJWtP”
).body;

if (!cssFont || !css) return “”;

if (!state.theme) {
State.update({
theme: styled.div`
font-family: Manrope, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
${cssFont}
${css}
`,
});
}
const Theme = state.theme;

// OUTPUT UI

const getSender = () => {
return !state.sender
? “”
: state.sender.substring(0, 6) +
“…” +
state.sender.substring(state.sender.length – 4, state.sender.length);
};

return (

Stake Ether
Stake ETH and receive stETH while staking.
{state.sender && (

Available to stake


{state.balance ?? (!state.sender ? “0” : “…”)} ETH

{getSender()}

>
)}

Staked amount


{state.stakedBalance ?? (!state.sender ? “0” : “…”)}
 stETH

Lido APR
{state.lidoArp ?? “…”}%





State.update({ strEther: e.target.value })}
placeholder=”Amount”
/>

{
State.update({
strEther: (state.balance > 0.05
? parseFloat(state.balance) – 0.05
: 0
).toFixed(2),
});
}}
>

{!!state.sender ? (

) : (

)}

{state.sender && (

You will receive
${state.strEther ?? 0} stETH

)}

Exchange rate
1 ETH = 1 stETH

{false && (

Transaction cost
{state.txCost}

)}

Reward fee
10%

);
“`

:::danger ETH Disabled in Docs
For a working example visit the deployed NEAR Component.
::

Next Steps

Build and deploy your first components without leaving the browser. Go to https://app.jutsu.ai/, create an account and start building!

Scroll to Top