NEAR Components are a new way to build web applications. They are composable, reusable and decentralized.
ou can login to interact with the examples in this section.
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) =>
{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 (
> {!!state.sender ? ( )} {false && ( )}
{state.balance ?? (!state.sender ? “0” : “…”)}Â ETH
)}
{state.stakedBalance ?? (!state.sender ? “0” : “…”)}
 stETH
State.update({ strEther: e.target.value })}
placeholder=”Amount”
/>
{
State.update({
strEther: (state.balance > 0.05
? parseFloat(state.balance) – 0.05
: 0
).toFixed(2),
});
}}
>
) : (
)}
“`
- :::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!