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 };
Related
I have 2 React context providers, both of which contain actions functions that update their state. I need to call one of these action function from the parent react context, but the context is undefined because it's not wrapped by the provider.
I cannot change the order of the wrapped providers. I also cannot put all the code inside the same provider (the code would be too large and not separated by concern).
import React, {useContext, createContext} from 'react'
const AAAAContext = createContext()
const BBBBContext = createContext()
function AAAAProvider(props) {
const bbbbContext = useContext(BBBBContext) // this is always undefined because not wrapped in BBBBProvider
const aaaaActions = {
doSomething: () => {
bbbbContext.bbbbActions.doSomething()
}
}
const aaaaContext = {aaaaActions}
return <AAAAContext.Provider value={aaaaContext}>{props.children}</AAAAContext.Provider>
}
function BBBBProvider(props) {
const aaaaContext = useContext(AAAAContext)
const bbbbActions = {
doSomething: () => {
aaaaContext.aaaaActions.doSomething()
}
}
const bbbbContext = {bbbbActions}
return <BBBBContext.Provider value={bbbbContext}>{props.children}</BBBBContext.Provider>
}
function App() {
return (
<AAAAProvider>
<BBBBProvider>
Hello
</BBBBProvider>
</AAAAProvider>
)
}
One possible way is to store child context in the parent context, and every time the child context changes in useEffect, call a function in the parent to update the child context in the parent context.
import React, {useContext, createContext, useState, useEffect} from 'react'
const AAAAContext = createContext()
const BBBBContext = createContext()
function AAAAProvider(props) {
const [bbbbActions, setBBBBActions] = useState()
const aaaaActions = {
doSomething: () => {
bbbbActions.doSomething()
},
setBBBBActions
}
const aaaaContext = {aaaaActions}
return <AAAAContext.Provider value={aaaaContext}>{props.children}</AAAAContext.Provider>
}
function BBBBProvider(props) {
const aaaaContext = useContext(AAAAContext)
const bbbbActions = {
doSomething: () => {
console.log('bbbbActions.doSomething')
}
}
const bbbbContext = {bbbbActions}
useEffect(() => {
aaaaContext.aaaaActions.setBBBBActions(bbbbActions)
}, [])
return <BBBBContext.Provider value={bbbbContext}>{props.children}</BBBBContext.Provider>
}
function Component() {
const aaaaContext = useContext(AAAAContext)
window.aaaaContext = aaaaContext
return 'Hello'
}
function App() {
const aaaaContext = useContext(AAAAContext)
window.aaaaContext = aaaaContext
return (
<AAAAProvider>
<BBBBProvider>
<Component/>
</BBBBProvider>
</AAAAProvider>
)
}
export default App
So I have set two datepickers which needs to be reformatted to MM-YYYY which I done with the following code:
const [dateFrom, setDateFrom ] = useState('2012-01-01')
const [dateTill, setDateTill ] = useState('2012-12-31')
const [yearFrom, monthFrom, dayFrom] = dateFrom.split('-');
const newDateFrom = `${monthFrom}-${yearFrom}`
const [yearTill, monthTill, dayTill] = dateTill.split('-');
const newDateTill = `${monthTill}-${yearTill}`
export default {newDateFrom, newDateTill}
I want to use the newDateFrom and newDateTill variables in my api file located in my api folder. I have used string interpolation here.
export const fetchTimeData = async () => {
const response = await fetch(`https://api.punkapi.com/v2/beers?brewed_before=${newDateTill}&brewed_after=${newDateFrom}&per_page=80`)
const data = await response.json();
return data
}
How can I get this to work? I'm not sure if props would work as there isn't a parent-child relationship going on here?
Thank you
You can't call the useState function in the global scope; if you want the variables returned from useState to be accessible to other functions, you need to make a custom hook like the following:
export function useNewDate() {
const [dateFrom, setDateFrom ] = useState('2012-01-01')
const [dateTill, setDateTill ] = useState('2012-12-31')
const [yearFrom, monthFrom, dayFrom] = dateFrom.split('-');
const newDateFrom = `${monthFrom}-${yearFrom}`
const [yearTill, monthTill, dayTill] = dateTill.split('-');
const newDateTill = `${monthTill}-${yearTill}`
return {newDateFrom, newDateTill}
}
Then, because you're using hooks, your fetchTimeData also needs to be a hook:
import { useNewDate } from './useNewDate' // or whichever file your useNewDate hook is stored in
import { useState, useEffect } from 'react';
export const useFetchTimeData = () => {
const { newDateFrom, newDateTill } = useNewDate()
const [timeData, setTimeData] = useState(null)
useEffect(() => {
(async () => {
const response = await fetch(`https://api.punkapi.com/v2/beers?brewed_before=${newDateTill}&brewed_after=${newDateFrom}&per_page=80`)
const data = await response.json();
setTimeData(data)
})();
}, [])
return timeData
}
Then, whenever you want to fetch the time data from within a React component, you would import this hook:
import { useFetchTimeData } from './useFetchTimeData'
export function MyComponent() {
const timeData = useFetchTimeData();
if (timeData === null) {
return <div>Loading time data...</div>
}
return <div>{JSON.stringify(timeData)}</div>
}
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.
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;