I am trying to use a smart contract just after I instantiate it. However, I get an error:
Uncaught (in promise) TypeError: Cannot read properties of null
(reading 'call')
I thought I did not have access to the functions just after instantiating it, but it does not seem to be it. What else could it be? Someone already had a similar problem?
Current code:
import React, { useEffect, useState } from 'react';
import Head from 'next/head';
import { useWeb3React } from '#web3-react/core';
import NFTCollectionArtifact from 'artifacts/contracts/NFTCollection.sol/NFTCollection.json';
import { Contract, ethers, Signer } from 'ethers';
import { Provider } from '#openmint/utils/provider';
export const Index = () => {
const context = useWeb3React<Provider>();
const { library } = context;
const [contractInstance, setContractInstance] = useState<Contract | undefined>();
const [signer, setSigner] = useState<Signer>();
const [name, setName] = useState<String>('');
useEffect(() => {
const address = '0x5FbDB2315678afecb367f032d93F642f64180aa3';
const nftCollection = new ethers.Contract(
address,
NFTCollectionArtifact.abi,
signer
);
setContractInstance(nftCollection);
}, []);
useEffect(() => {
if(!contractInstance) return;
const setContractName = async () => {
try{
console.log(await contractInstance?.name());
setName(await contractInstance?.name());
} catch(e){
console.error('my error', e);
}
}
setContractName();
}, [contractInstance]);
useEffect((): void => {
if (!library) {
setSigner(undefined);
return;
}
setSigner(library.getSigner());
}, [library]);
return (
<>
<Head>
<title>Preview NFTs</title>
</Head>
</>
);
};
export default Index;
Ok, I still do not understand the whole picture of why the following solution works, but if anyone out there is having a similar issue, this was what worked for me:
useEffect((): void => {
if (!contractInstance) {
return;
}
async function getStatus(MyContract: Contract): Promise<void> {
const name = await MyContract.name();
const newStatus: StatusInterface = {
...status,
name
};
console.log('newStatus', newStatus);
if (!isEqual(status, newStatus)) {
setStatus(newStatus);
}
}
getStatus(contractInstance);
}, [contractInstance, status, account]);
Only change the state if there is a difference
Pass the contract as a prop for the function
My understanding is that this was a scope issue.
Make sure your environment config has an account set up correctly. In my case this was happening because I had an extra WALLET_PRIVATE_KEY= line set in my .env when using ethers + hardhat.
Related
I have an issue with react won't update ui, I am trying to update the number of people connected in the same room there's no issue in the backend, my issue is on the front because I saw that the events are reaching the client through chrome dev tools.
as shown below the event is indeed reaching the client.
import React, { useContext, useEffect, useState, useCallback } from 'react';
import { useParams } from 'react-router-dom';
import { SocketContext } from '../context/socket';
type Props = {};
export default function Game({}: Props) {
const socket = useContext(SocketContext);
const { id } = useParams();
const [playerCount, setPlayerCount] = useState(0);
const updatePlayerCount = (...args: string[]) => {
console.log(args);
setPlayerCount(args.length);
};
useEffect(() => {
socket.emit('join_room', id);
socket.on('game_result', gameHandler);
socket.on('player_count', updatePlayerCount);
return () => {
socket.off('game_result');
socket.off('player_count');
};
}, []);
const gameHandler = (...args: any) => {
console.log(args);
};
return (
<div>
Game {id}
<div>{playerCount}</div>
</div>
);
}
checking the console I do see my console.log firing...
however the first join event does work cause I don't see 0 I see 1 instead. playerCount = 0 initially
You can try defining both your event handlers inside your useEffect() like this
useEffect(() => {
const updatePlayerCount = (...args: string[]) => {
console.log(args);
setPlayerCount(args.length);
};
const gameHandler = (...args: any) => {
console.log(args);
};
socket.emit('join_room', id);
socket.on('game_result', gameHandler);
socket.on('player_count', updatePlayerCount);
return () => {
socket.off('game_result');
socket.off('player_count');
};
}, [id]);
apparently I didn't pay attention that the data emitted was an array embedded inside an array
a simple fix was
setPlayerCount(args[0].length);
I'm using next with typescript and trying to load a webworker.
For some reason - when I'm trying to initialize the webworker when I'm creating a hook - next tells me that Worker is not defined.
I'm using comlink but I suspect it's an issue with Next since comlink isn't in the mix at the point of error.
You can find the problematic line in below code snippet useWorker.hooks.ts in the function makeWorkerApiAndCleanup
Does anyone understand why this is happening?
const worker = new Worker(url);
useWorker.hooks.ts
import { wrap, releaseProxy } from "comlink";
import { useEffect, useState, useMemo } from "react";
function makeWorkerApiAndCleanup() {
const url = new URL("./useWorker.worker", import.meta.url)
const worker = new Worker(url);
const workerApi = wrap<import("./useWorker.worker").WorkerType>(worker);
const cleanup = () => {
workerApi[releaseProxy]();
worker.terminate();
};
const workerApiAndCleanup = { workerApi, cleanup };
return workerApiAndCleanup;
}
function useWorker() {
const workerApiAndCleanup = useMemo(() => makeWorkerApiAndCleanup(), []);
useEffect(() => {
const { cleanup } = workerApiAndCleanup;
return () => {
cleanup();
};
}, [workerApiAndCleanup]);
return workerApiAndCleanup;
}
function useImplementation() {
const [data, setData] = useState({});
const { workerApi } = useWorker();
useEffect(() => {
workerApi.workerFunction();
setData({});
}, [workerApi]);
return data;
}
export default useImplementation;
useWorker.worker.ts
import { expose } from 'comlink';
import { workerFunction } from './functions';
const worker = {
workerFunction,
};
export type WorkerType = typeof worker;
expose(worker);
useWorker/index.ts
import useWorker from './useWorker.hooks';
export { useWorker };
I have used React 17, MobX and Typescript in my project.
In postDetails.tsx file, I want to show the article but I got this error:
TypeError: Cannot read property 'Description' of undefined.
While the data is available, the status cannot be updated!
(I just found out that I did not receive the updated status. It means that I receive the last value of the state.)
How can I solve the problem?
If I comment this line of code, {/* <h1>{(postStore.postDetails.Description)}</h1> */}, you can see the order of the execution line code. It's not correspondig to the order of line code!
import React, { useEffect } from "react";
import { useParams } from "react-router-dom";
import { observer } from "mobx-react-lite";
import { useStore } from "../../app/stores/store";
import "./postDetails.css";
import { runInAction, toJS } from "mobx";
export default observer(function PostDetails() {
const { postStore } = useStore();
let { id } = useParams<{ id?: string }>();
console.log("0001 - before useEffect");
useEffect(() => {
console.log("0002 - in useEffect");
try {
runInAction(async () => {
await postStore.details(id);
})
console.log("0003 - after executing postStore.details(id)");
console.log("postStore.selectedPost : ", toJS(postStore.selectedPost));
}
catch (error) { }
}, [id, postStore]);
console.log("0004 - right after the useEffect");
console.log("just befor return: ", postStore.postDetails);
return (
<>
{console.log("0005 - I expected first run useEffect for getting the data.")}
/* The data is not available */
{<h1>{(postStore.postDetails.Description)}</h1>}
</>
)
})
postStore.ts:
selectedPost: IPostModel | undefined = undefined;
details = async (id: string) => {
this.loadingInitial = true;
try {
const detailPost = await agent.Post.details(id);
runInAction(() => {
this.selectedPost = detailPost;
})
}
catch (error) {
console.log(error);
this.setLoadingInitial(false);
}
}
In addition to my comment: your code should be something like that:
export default observer(function PostDetails() {
const { postStore } = useStore();
let { id } = useParams<{ id?: string }>();
useEffect(() => {
postStore.details(id)
}, [id, postStore]);
if (!postStore.postDetails) {
return <div>Loading...</div>
}
return (
<h1>{(postStore.postDetails.Description)}</h1>
)
})
Just handle the case when details is not yet available.
Or you can add isLoading flag to your store to make it more transparent what is going on.
I have a React Native App,
Here i use mobx ("mobx-react": "^6.1.8") and react hooks.
i get the error:
Invalid hook call. Hooks can only be called inside of the body of a function component
Stores index.js
import { useContext } from "react";
import UserStore from "./UserStore";
import SettingsStore from "./SettingsStore";
const useStore = () => {
return {
UserStore: useContext(UserStore),
SettingsStore: useContext(SettingsStore),
};
};
export default useStore;
helper.js OLD
import React from "react";
import useStores from "../stores";
export const useLoadAsyncProfileDependencies = userID => {
const { ExamsStore, UserStore, CTAStore, AnswersStore } = useStores();
const [user, setUser] = useState({});
const [ctas, setCtas] = useState([]);
const [answers, setAnswers] = useState([]);
useEffect(() => {
if (userID) {
(async () => {
const user = await UserStore.initUser();
UserStore.user = user;
setUser(user);
})();
(async () => {
const ctas = await CTAStore.getAllCTAS(userID);
CTAStore.ctas = ctas;
setCtas(ctas);
})();
(async () => {
const answers = await AnswersStore.getAllAnswers(userID);
UserStore.user.answers = answers.items;
AnswersStore.answers = answers.items;
ExamsStore.initExams(answers.items);
setAnswers(answers.items);
})();
}
}, [userID]);
};
Screen
import React, { useEffect, useState, useRef } from "react";
import {
View,
Dimensions,
SafeAreaView,
ScrollView,
StyleSheet
} from "react-native";
import {
widthPercentageToDP as wp,
heightPercentageToDP as hp
} from "react-native-responsive-screen";
import { observer } from "mobx-react";
import useStores from "../../stores";
import { useLoadAsyncProfileDependencies } from "../../helper/app";
const windowWidth = Dimensions.get("window").width;
export default observer(({ navigation }) => {
const {
UserStore,
ExamsStore,
CTAStore,
InternetConnectionStore
} = useStores();
const scrollViewRef = useRef();
const [currentSlide, setCurrentSlide] = useState(0);
useEffect(() => {
if (InternetConnectionStore.isOffline) {
return;
}
Tracking.trackEvent("opensScreen", { name: "Challenges" });
useLoadAsyncProfileDependencies(UserStore.userID);
}, []);
React.useEffect(() => {
const unsubscribe = navigation.addListener("focus", () => {
CTAStore.popBadget(BadgetNames.ChallengesTab);
});
return unsubscribe;
}, [navigation]);
async function refresh() {
const user = await UserStore.initUser(); //wird das gebarucht?
useLoadAsyncProfileDependencies(UserStore.userID);
if (user) {
InternetConnectionStore.isOffline = false;
}
}
const name = UserStore.name;
return (
<SafeAreaView style={styles.container} forceInset={{ top: "always" }}>
</SafeAreaView>
);
});
so now, when i call the useLoadAsyncProfileDependencies function, i get this error.
The Problem is that i call useStores in helper.js
so when i pass the Stores from the Screen to the helper it is working.
export const loadAsyncProfileDependencies = async ({
ExamsStore,
UserStore,
CTAStore,
AnswersStore
}) => {
const userID = UserStore.userID;
if (userID) {
UserStore.initUser().then(user => {
UserStore.user = user;
});
CTAStore.getAllCTAS(userID).then(ctas => {
console.log("test", ctas);
CTAStore.ctas = ctas;
});
AnswersStore.getAllAnswers(userID).then(answers => {
AnswersStore.answers = answers.items;
ExamsStore.initExams(answers.items);
});
}
};
Is there a better way? instead passing the Stores.
So that i can use this function in functions?
As the error says, you can only use hooks inside the root of a functional component, and your useLoadAsyncProfileDependencies is technically a custom hook so you cant use it inside a class component.
https://reactjs.org/warnings/invalid-hook-call-warning.html
EDIT: Well after showing the code for app.js, as mentioned, hook calls can only be done top level from a function component or the root of a custom hook. You need to rewire your code to use custom hooks.
SEE THIS: https://reactjs.org/docs/hooks-rules.html
You should return the value for _handleAppStateChange so your useEffect's the value as a depdendency in your root component would work properly as intended which is should run only if value has changed. You also need to rewrite that as a custom hook so you can call hooks inside.
doTasksEveryTimeWhenAppWillOpenFromBackgorund and doTasksEveryTimeWhenAppGoesToBackgorund should also be written as a custom hook so you can call useLoadAsyncProfileDependencies inside.
write those hooks in a functional way so you are isolating specific tasks and chain hooks as you wish without violiating the rules of hooks. Something like this:
const useGetMyData = (params) => {
const [data, setData] = useState()
useEffect(() => {
(async () => {
const apiData = await myApiCall(params)
setData(apiData)
})()
}, [params])
return data
}
Then you can call that custom hook as you wish without violation like:
const useShouldGetData = (should, params) => {
if (should) {
return useGetMyData()
}
return null
}
const myApp = () => {
const myData = useShouldGetData(true, {id: 1})
return (
<div>
{JSON.stringify(myData)}
</div>
)
}
I'm trying to set an object then use it immediately but it is saying that it's null. I can observe in debug mode that the instantiated object is not null.
I could just use the instantiated objected instead of the constant but I was advised not to.
import { useState, useEffect } from "react";
import { loadModules } from "#esri/react-arcgis";
const TestLayer = props => {
const [layer, setLayer] = useState(null);
useEffect(() => {
loadModules(["esri/layers/GraphicsLayer"])
.then(([GraphicsLayer]) => {
const graphicsLayer = new GraphicsLayer();
setLayer(graphicsLayer);
props.map.layers.add(layer); //layer is still null
});
}, []);
return null;
};
export default TestLayer;
Yeah, it's async like the comments suggest. The good news is that where you're trying to access it synchronously is the same place that you have access to the original object, so you can just use it directly:
import { useState, useEffect } from "react";
import { loadModules } from "#esri/react-arcgis";
const TestLayer = props => {
const [layer, setLayer] = useState(null);
useEffect(() => {
loadModules(["esri/layers/GraphicsLayer"]).then(([GraphicsLayer]) => {
const graphicsLayer = new GraphicsLayer();
setLayer(graphicsLayer);
props.map.layers.add(graphicsLayer); // Don't need to access state here
});
}, []);
return null;
};
export default TestLayer;