web3js intercepting when metamask connects manually - javascript

Currently I have the following code to connect the metamask wallet in the browser (using reactjs and web3js):
function App() {
const [contract, setContract] = useState();
const [account, setAccount] = useState();
const[web3Obj, setWeb3Obj]=useState();
useEffect(()=>{
async function load(){
const web3 = new Web3(Web3.givenProvider || 'http://http://localhost:7545');
setWeb3Obj(web3);
const accounts = await web3.eth.requestAccounts();
console.log(accounts[0] + " is the account");
setAccount(accounts[0]);
$.get( "getcontractaddress")
.done( function( data ) {
const _contract = new web3.eth.Contract(abi, data);
_contract.address = data;
console.log(_contract.address + " is the contract");
setContract(_contract);
});
}
load();
},[])
return(
);
}
I deleted the return(); part from the snippet because its irrelevant to the question.
Whenever I switch to a different account in metamask, the "account" object is not being updated. How can I intercept the event of metamask switching accounts to automatically reset the account object to the new account?

Your component does not know when you switch accounts in Metamask. To detect account changes, Metamask provides the accountsChanged event.
ethereum.on('accountsChanged', handler: (accounts: Array<string>) => void);
You can add the event to your load function or make another useEffect. You can also remove the event when your component unmounts using ethereum.removeListener.
useEffect(() => {
const { ethereum } = window;
if (ethereum && ethereum.on) {
const handleAccountsChanged = (accounts) => {
console.log("accountsChanged", accounts);
setAccount(accounts[0])
};
ethereum.on('connect', handleConnect);
return () => {
if (ethereum.removeListener) {
ethereum.removeListener('accountsChanged', handleAccountsChanged);
}
};
}
}, []);

Related

How to prevent socketIO from emitting duplicate data in React Js

When I'm emitting message from client side.
I'm receiving lot of same duplicated message
on server side or in my message displaying container div here is the code
Global Socket Provider Context
A simple context which provide socket to all children or components
// fileName : SocketProvider.js
import { createContext } from "react";
import { io } from "socket.io-client";
const socket = io("http://localhost:8080/", { withCredentials: true });
const SocketContext = createContext();
export const SocketProvider = ({ children }) => {
return (
<SocketContext.Provider value={{ socket }}>
{children}
</SocketContext.Provider>
);
};
export default SocketContext;
And I've wrapped SocketProvider in App.js like this
<SocketProvider>
<Main/>
</SocketProvider>
In Dashboard Page or Main Page
Where I've div for displaying messages and input message box whenever user press enter or click send button which is img I'm emitting a message here is the code
// filename : Main.jsx
// all things imported correctly
import ...
const Main = ()=> {
const { socket } = useContext(SocketContext);
const [message, setMessage] = useState("");
const [conversation, setConversation] = useState([]);
// another global context
const { currentUserId } = useContext(UserContext);
const { selectedUserId, setSelectedUserId } = useContext(SelectedUserContext);
// code for changing selectedUserId
// ...
...
.... //
// on start sending userJoined
useEffect(() => {
socket.emit("userJoined", {
senderUserId: currentUserId,
receiverUserId: selectedUserId,
});
}, [socket, currentUserId, selectedUserId]);
// useEffect for listening messages
useEffect(() => {
const onMessageReceived = (msg) => {
setConversation((conversations) => [...conversations, msg]);
};
socket.on("message", onMessageReceived);
return () => {
socket.off("message", onMessageReceived);
};
}, [socket]);
// this function will send message
const sendMessage = useCallback(() => {
if (!message) return;
socket.emit("message", {
msg: message,
sentBy: "Unknown"
});
setMessage("");
}, [socket, message]);
// on enter btn pressed
useEffect(() => {
const handleEnterKeyPressed = (e) => {
if (e.keyCode === 13 && e.shiftKey === false && e.ctrlKey === false)
sendMessage();
};
element.addEventListener("keydown", handleEnterKeyPressed);
return () => {
element.removeEventListener("keydown", handleEnterKeyPressed);
};
}, [sendMessage]);
return (
<div className="container">
<div className="msgContainer">
{conversation.map((data)=>((
<h1 className="...">{data.msg}</h1>
<p className="...">{data.sentBy}</p>
)}
</div>
<input
value={message}
onChange={(e)=>setMessage(e.target.value)}
placeholder="Enter message here"/>
<img className="..." src={sendIcon} onClick={sendMessage}>
</div>
);
}
export default Main;
Code In Server Side Where I'm Listening for message
//filename: server.js
io.on("connection", (socket) => {
socket.on("userJoined", (userJoinedData) => {
const chatRoomId = generateChatRoomId(userJoinedData);
socket.on("message", async (msgData) => {
// this console log many times or more than 11
console.log(`DEBUG: New Message Arrived :`, msgData);
// saving msg to mongo db
const savedMessage = await saveMsg(msgData);
io.to(chatRoomId).emit("message", savedMessage);
});
});
});
and even I got following warning in terminal :
(node:14040) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 message listeners added
to [Socket]. Use emitter.setMaxListeners() to increase limit
Any help will be always appreciated :)
As #NickParsons mentioned -
Is that peice of code being executed multiple times?
Whenever currentUserId or selectedUserId changes you are emiting userJoined in server code. And userJoined listener on server side get called and your message listener listen again your data so that's why you are getting duplicated data.
To fix this you have to place message listener outside of userJoined listener!
I know you are getting some info of userJoinedData so that's why you have place message listener inside userJoined.
So you have to also pass that userJoinedData while emitting message in your client side and change code in your server side like this -
//filename: server.js
io.on("connection", (socket) => {
socket.on("userJoined", (userJoinedData) => {
const chatRoomId = generateChatRoomId(userJoinedData);
// .. your other codes
});
// but place message listener outside here
socket.on("message", async (msgData, userJoinedData) => {
// now here also you can generate chatRoomId
const chatRoomId = generateChatRoomId(userJoinedData);
// saving msg to mongo db
const savedMessage = await saveMsg(msgData);
io.to(chatRoomId).emit("message", savedMessage);
});
});

Uncaught (in promise) ReferenceError: Buffer is not defined. Using Phantom Wallet, Solana and React to make a transaction

I created a simple CRA app to figure out how to use the Phantom wallet to make Solana transactions(I still don't know If that is even possible). I referenced two Stackoverflow questions source and source and came up with a somewhat working code as in the airdrop is working perfectly fine but the main transaction is not.
This is my code:
import "./App.css";
import { useEffect, useState } from "react";
const web3 = require("#solana/web3.js");
function App() {
const [provider, setProvider] = useState(null);
const [walletKey, setWalletKey] = useState(null);
const getProvider = () => {
if ("solana" in window) {
const provider = window.solana;
if (provider.isPhantom) {
return provider;
}
}
};
const connectWallet = async () => {
const provider = getProvider();
if (provider) {
try {
const response = await provider.connect();
const pubKey = await provider.publicKey;
console.log(pubKey);
setProvider(provider);
setWalletKey(response.publicKey.toString());
} catch (err) {
// { code: 4001, message: 'User rejected the request.' }
}
}
};
useEffect(() => connectWallet, []);
const airDropSol = async (connection, publicKey) => {
try {
const airdropSignature = await connection.requestAirdrop(
publicKey,
web3.LAMPORTS_PER_SOL
);
const latestBlockHash = await connection.getLatestBlockhash();
// Confirming that the airdrop went through
await connection.confirmTransaction({
blockhash: latestBlockHash.blockhash,
lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
signature: airdropSignature,
});
console.log("Airdropped");
} catch (error) {
console.error(error);
}
};
async function transferSOL() {
//Changes are only here, in the beginning
const phantomProvider = provider;
if (!phantomProvider) {
console.log("No provider found", phantomProvider);
}
const pubKey = await phantomProvider.publicKey;
console.log("Public Key: ", pubKey);
// Establishing connection
var connection = new web3.Connection(web3.clusterApiUrl("devnet"));
// I have hardcoded my secondary wallet address here. You can take this address either from user input or your DB or wherever
var recieverWallet = new web3.PublicKey(
"Wallet Key I want to send SOL to here"
);
// Airdrop some SOL to the sender's wallet, so that it can handle the txn fee
airDropSol(connection, pubKey);
console.log("WORKED 1");
const transaction = new web3.Transaction();
const instructions = web3.SystemProgram.transfer({
fromPubkey: pubKey,
toPubkey: recieverWallet,
lamports: web3.LAMPORTS_PER_SOL, //Investing 1 SOL. Remember 1 Lamport = 10^-9 SOL.
});
transaction.add(instructions);
console.log("WORKED 2");
// Setting the variables for the transaction
transaction.feePayer = pubKey;
let blockhashObj = await connection.getLatestBlockhash();
transaction.recentBlockhash = blockhashObj.blockhash;
const signature = await phantomProvider.signAndSendTransaction(
connection,
transaction,
[pubKey]
);
await connection.confirmTransaction(signature);
console.log(signature);
}
return (
<div className="App">
<header className="App-header">
<h2>Tutorial: Connect to Phantom Wallet</h2>
{provider && walletKey && <p>Connected account {walletKey}</p>}
{provider && walletKey && <button onClick={transferSOL}>TEST</button>}
{!provider && (
<p>
No provider found. Install{" "}
Phantom Browser extension
</p>
)}
</header>
</div>
);
}
export default App;
This is the error generated in and I don't know how to fix it. Can anyone help? Error in browser console
Also when I run npm start this error in vcode console also gets shown.
All I want to do is simply send SOL from one phantom wallet to another like sending money in real life, I have spent alot of time trying to figure this out someone please point out the problem and help
Use #solana/web3.js version 1.30.2
New versions form web3 libraries need some dependencies and webpack configurations.
You can use a code I'm sharing:-
Refer my code on this link
With Webpack 5 you need to polyfill Buffer.
Here is a tutorial on how to do so https://viglucci.io/how-to-polyfill-buffer-with-webpack-5
And here is an example of a webpack config of a Solana dApp UI made with CRA https://github.com/Bonfida/serverless-merch/blob/master/ui/config-overrides.js

Websocket is undefined immediately after receiving message in react

I'm using websocket in react. This is the code for the component:
import React, { useState, useEffect } from "react";
export default function Component(props) {
const [socket, setSocket] = useState();
const parseMessage = (msg) => {
if (msg[0] !== "R") sendMessage("123"); // ignore the very first message from the socket.
};
const sendMessage = (msg) => socket.send(msg); // error at this line
useEffect(() => {
const socket = new WebSocket("wss://ws.ifelse.io/");
socket.addEventListener("message", ({ data }) => {
if (socket) parseMessage(data);
});
setSocket(socket);
}, []);
const sendMsg = () => {
socket.send("test");
};
return <button onClick={() => sendMsg("clicked")}>send msg</button>;
}
I'm getting this error: TypeError: Cannot read properties of undefined (reading 'send') at the marked line. The WebSocket is just an echo server, it sends back the same thing you send it.
If I wrap the socket.send in a try-catch block, I can still send and receive messages from the WebSocket using the button, but the error still occurs at that line in sendMessage.
It's clear that the socket variable is not undefined as I'm able to send and receive messages before and after the error occurs.
So my question is why is the socket variable undefined only for the brief period after receiving a message, and what is the fix for this issue.
Better solution would be to initialize the socket outside
import React, { useState, useEffect } from "react";
const socket = new WebSocket("wss://ws.ifelse.io/")
export default function Component(props) {
const ws = useRef(socket)
useEffect(() => {
ws.current?.addEventListener("message", ({ data }) => {
parseMessage(data);
});
return () => {
ws.current?.removeAllListeners()
}
}, [])
const parseMessage = (msg) => {
if (msg[0] !== "R") sendMessage("123"); // ignore the very first message from the socket.
};
const sendMessage = (msg) => ws.current?.send(msg);
const sendMsg = () => {
ws.current?.send("test");
};
return <button onClick={() => sendMsg("clicked")}>send msg</button>;
}
The useEffect runs just once and in that moment the socket is still undefined. Function sendMessage references undefined socket when the effect runs. When the socket is set using setSocket, component will rerender, but the new instance of sendMessage (now referencing the existing socket) will never be used, because the effect will not run again.
It is better to use ref in this case.
export default function Component(props) {
const socket = useRef();
const sendMessage = (msg) => socket.current.send(msg);
useEffect(() => {
socket.current = new WebSocket("wss://ws.ifelse.io/");
socket.current.addEventListener("message", ({ data }) => {
parseMessage(data);
});
return () => {
... release resources here
}
}, []);
...
}
When you use useState you create a state variable with an update state function. You also provide the initial value of the state variable in useState(initialState).
In your case you are not passing anything (i.e. you are telling React it's undefined, that's why at this line:
const sendMessage = (msg) => socket.send(msg); // error at this line
your code cannot use socket.send because socket is undefined.
Your useEffect runs after the return function and then the socket instance is created as well as set to socket state variable.
You need to make sure that you are only sending message over socket after socket is created.
For more on useEffect you can read this post:
https://jayendra.xyz/2019/06/17/how-useeffect-works/
You need to wait for the socket to be established first and then set the socket using setSocket in the open event. The way you are doing it now expects the first message to be sent by the server and then you setSocket, if the server does not send a message before you click on the button, the socket instance is not set in your state.
Do this instead:
socket.addEventListener('open', function (event) {
setSocket(socket);
});
Here is the full code
import React, { useState, useEffect } from "react";
export default function Component(props) {
const [socket, setSocket] = useState();
const [disabled, setButtonDisabled] = useState(true);
const parseMessage = (msg) => {
if (msg[0] !== "R") sendMessage("123"); // ignore the very first message from the socket.
};
const sendMessage = (msg) => socket.send(msg); // error at this line
useEffect(() => {
const socket = new WebSocket("wss://ws.ifelse.io/");
socket.addEventListener('open', function (event) {
setSocket(socket);
setButtonDisabled(false);
});
socket.addEventListener("message", ({ data }) => {
if (data) parseMessage(data);
});
return ()=>{
socket.close();
};
}, []);
const sendMsg = () => {
if(socket) socket.send("test");
};
return <button disabled={disabled} onClick={() => sendMsg("clicked")}>send msg</button>;
}

Hooks and webworkers

Is there a way for a webworker to use a react hook?
I am using Apollo Client to perform useLazyQuery which is a custom hook.
But the actual operation takes quite long and times out most often!
I want to run this on another thread to not disrupt the main application.
Although I'm not sure whether your timeout problem would be fixed by executing the query in a webworker, the easiest way to achieve that (without considering pending/error states) would be something like this:
worker.js
self.addEventListener('message', async event => {
const {apolloClient, query} = e.data;
const result = await apolloClient.query({query});
self.postMessage(result);
self.close();
});
App.jsx
const query = gql`Some query here`;
const useWebWorkerQuery = query => {
const apolloClient = useApolloClient();
const [result, setResult] = useState(null);
useEffect(() => {
const worker = new Worker('worker.js');
worker.postMessage({apolloClient, query});
worker.onmessage = event => setResult(event.data);
}, []);
};
const App = () => {
const result = useWebWorkerQuery(query);
useEffect(() => {
if(result) {
// Do something once query completes
}
}, [result]);
return null;
};
export default App;

How to configure React Native Linking?

I am using deep linking for handling notifications in my app. For remote notifications I use React Native Firebase and for local notifications I use react-native-push-notification. I am handling background and quit state notifications with react-navigation's deep linking system, but for foreground notifications I use react-native's Linking and this is where the problem is. Linking.canOpenUrl returns supported but Linking.openUrl does nothing. I already added LSApplicationQueriesScheme key to my info.plist and added my prefix to it.
this is for foreground handling =>
const onOpenNotification = async (data) => {
const messagesObj = await AsyncStorage.getItem('messages')
const messages = JSON.parse(messagesObj)
const notificationData = messages[data.id]
Linking.canOpenURL(notificationData.link).then(supported => {
if (supported) {
console.log("supported!")
Linking.openURL(notificationData.link)
}
else {
console.log("Cannot open! ")
}
}).catch(err => console.log(err))
}
this is for background and quit state handling =>
export const notificationLinking = {
prefixes: ['rateet://app'],
config,
async getInitialUrl(){
// Check if app was opened from a deep link
const url = await Linking.getInitialURL();
if (url != null) {
return url;
}
// Check if there is an initial firebase notification
const message = await messaging().getInitialNotification();
// Get the `url` property from the notification which corresponds to a screen
// This property needs to be set on the notification payload when sending it
return message?.data.link;
},
subscribe(listener){
const onReceiveURL = (url) => listener(url);
// Listen to incoming links from deep linking
Linking.addEventListener('url', onReceiveURL);
// Listen to firebase push notifications
const unsubscribeNotification = messaging().onNotificationOpenedApp(
(message) => {
const url = message.data.link;
if (url) {
// Any custom logic to check whether the URL needs to be handled
//...
// Call the listener to let React Navigation handle the URL
listener(url);
}
}
);
return () => {
// Clean up the event listeners
Linking.removeEventListener('url', onReceiveURL);
unsubscribeNotification();
}
}
}

Categories

Resources