Metamask를 연결하여 NFT MarketPlace를 구현하던 중 예기치 못한 오류가 발생했습니다.
공식 문서를 보면서 계정 및 체인 네트워크 전환이 될 때 함수를 실행하는 로직을 구현했는데 계속 callback
중첩되어 발생하는 이슈였습니다. 이 글에서는 이 이슈를 해결했던 방법을 알아보겠습니다.
먼저 공식 문서에서 제공하는 자료를 확인해보겠습니다.
ethereum.on('accountsChanged', handler: (accounts: Array<string>) => void);
동작 원리는 이렇습니다.
eth_accountsRPC 메서드의 리턴 값이 변경될 때마다 MetaMask에서 이를 감지하고 있다가 변경 된 값을 리턴합니다. 즉, accountsChanged 사용자의 계정 주소가 변경될 때마다 리턴됨을 의미합니다.
네.. 끝입니다.
사실 이는 Event Listener
와 같습니다. 그래서 사실 위에있는 코드만 작성하고 react
에서 확인하게되면 계정이 변경될 때(리렌더링 될 때)마다 Event Listener
가 쌓이게 됩니다.
그럼 어떻게 될까요?
처음에는 한 번 callback
을 실행 시켰던 로직이 계정 변경을 여러번 하면 할 수록 callback을 중첩해서 실행하게 됩니다. (Event Listener는 같은 노드에 대해 중첩이 가능합니다)
계정이 변경될 때 단 한 번만 callback을 실행되는 것을 예상한 로직이 여러번 반복되는 것은 의도한 로직이 아닙니다.
따라서 저는 아래와 같이 코드를 작성하여 해결했습니다.
const handleAccountChange = (...args) => {
const account = args[0][0];
if (!account) {
} else if (account !== currentAccount) {
//변경된 계정이 현재 계정과 다름
//즉, 계정 변경 완료
}
};
useEffect(() => {
window.ethereum?.on('accountsChanged', handleAccountChange);
return () => {
window.ethereum?.removeListener('accountsChanged', handleAccountChange);
};
});
useEffect를 사용하여 컴포넌트가 마운트 됐을 때 (처음 나타났을 때) Event Listener
를 등록하고 언마운트 됐을 때 (사라질 때) 등록한 Event Listener
를 삭제해주었습니다.
Event Listener
가 중첩되지 않아 의도하지 않던 Side Effect
를 해결하였습니다.
마찬가지로 체인 네트워크 변경 로직도 살펴보겠습니다.
먼저 공식 문서에서 제공해주는 문법을 살펴보겠습니다.
ethereum.on('chainChanged', handler: (chainId: string) => void);
MetaMask는 현재 연결된 체인이 변경될 때 이 이벤트를 리턴합니다.
위와 같은 이유로 아래와 같이 코드를 작성하였습니다.
const handleNetworkChanged = (...args) => {
const networkId = args[0];
window.location.reload();
};
useEffect(() => {
window.ethereum?.on('networkChanged', handleNetworkChanged);
return () => {
window.ethereum?.removeListener('networkChanged', handleNetworkChanged);
};
});
한 가지 다른 점은 체인 네트워크 변경 시 window.location.reload()
를 이용하여 페이지를 다시 로드하는 것을 권장하고있습니다. 왜냐하면 모든 RPC 요청은 현재 연결된 체인 네트워크에 요청됩니다. 따라서 현재 체인 ID를 추적하는 것이 중요하기 때문입니다.