Where to load contract to prevent undefined methods when calling contract? - javascript

I am trying to run a method from my smart contract located on the ropsten test network but am running into the following error in my getBalance() function:
Unhandled Runtime Error
TypeError: Cannot read properties of undefined (reading 'methods')
46 | async function getBalance() {
> 47 | const tempBal = await window.contract.methods.balanceOf(account.data).call()
| ^
48 | setBalance(tempBal)
49 | }
Connecting my contract:
export default function ConnectContract() {
async function loadWeb3() {
if (window.ethereum) {
window.web3 = new Web3(window.ethereum)
window.ethereum.enable()
}
}
async function loadContract() {
const tempContract = await new window.web3.eth.Contract(ABI, ContractAddress)
return tempContract
}
async function load() {
await loadWeb3();
window.contract = await loadContract();
}
load();
}
Currently the functions are being called like so:
export default function Page() {
ConnectContract();
getBalance();
}
Another way I previous called it that did not result in the undefined error was by using an onClick function inside a button to getBalance:
<div><button onClick={() => getBalance}>Get Balance</button></div>
I know calling them like this is causing the getBalance function method to be called before the contract is connected to the website but with the current code, it works after 1-page refresh. But I have not been able to find a solution to make the contract be loaded so the method is defined by the time getBalance is called.
I have tried doing onLoad function and window.load() functions with no success. I have also tried calling my ConnectContract function in the _app.js to load it when the website is launched but that had no success as well.

You have to call the loadWeb3 function inside useEffect hook. useEffect is used to prepare the state for the component when it is mounted. to keep track of the state, use useState hook
const [web3,setWeb3]=useState('')
const [contract,setContract]=useState('')
async function loadWeb3() {
if (window.ethereum) {
window.ethereum.enable()
setWeb3(new Web3(window.ethereum))
}
}
// [] array means we want this useEffect code run only when the compoenent rerenders
// Since loadWeb3 is async function, call it inside try/catch block
useEffect(()=>{loadWeb3()},[])
Next we you need to prepare contract same way.
async function loadContract() {
const tempContract = await new web3.eth.Contract(ABI, ContractAddress)
setContract(tempContract)
}
You also need to call this in useEffect but this time its dependencies are different. Because in order to get the contract, we are relying on web3
// Since loadContract is async function, call it inside try/catch block
useEffect(()=>{loadContract},[web3])
when component renders, first useEffect will run and set the web3. Since web3 is changed, component will rerender again this useEffect will run. So when your component is mounted, you will have web3 and contract set so you can use those.

Your issue is in this code right here:
46 | async function getBalance() {
> 47 | const tempBal = await window.contract.methods.balanceOf(account.data).call()
| ^
48 | setBalance(tempBal)
49 | }
My guess is that the window.contract either doesn't exist or isn't getting loaded before this gets called. Perhaps it is either getting called in a serverside place where the window doesn't exist or try wrapping it in an if function so that it checks to make sure it exists before calling it

Related

How to use hooks within function in React Js

I have a file where I keep all my functions that make api calls, then I import these functions inside components to use where needed. The issue I'm facing is that when api call is made and it returns 'unauthorized user'(because token has expired) I need to redirect users to the login page.
// apiCalls.js file
export async function getData(){
let response = await fetch(url)
let res = response.json()
// here I need to add redirect to /login if res.status.code is 401 for example
}
I tried to create a custom hook (with useNavigate) to use inside the function, but app throws error saying that hooks can't be used inside function. I can check status of the request inside the component(after I import function from apiCalls.js) but it doesn't seem like a correct way of approaching this as I'll have to add check inside every component that may use this function. Any advise is greatly appreciated
EDITED: to add context, I need to redirect user from a function( not functional component), function is exported from apiCalls.js file.
There's a great way to use hooks inside of a function - make the function a hook! The caveat is that this function will also need to follow the rules of hooks; a big one being the one you've just discovered: you should only be calling it inside a component or other hooks. If you're getting an error because of eslint, you generally also have to (and should) prefix this function with use (like useGetData).
export function useGetData(){
const navigation = useNavigation();
const getData = async (url) => {
let response = await fetch(url)
let res = response.json()
if (/* isInvalidStatus */) {
// navigate to '/login'
}
// return your data
}
return { getData }
}
export function MyComponent() {
const { getData } = useGetData();
// call getData() from useEffect or a click handler
// return jsx;
}
Brief explanation in case it helps:.
First we'll rename the function to follow convention, but we'll also have to remove the async keyword (which we address later). We'll add the useNavigation hook.
export function useGetData() {
// or whatever navigator your router provides
const navigation = useNavigation();
}
The hook itself can't be async, but we can expose a function in the hook's return object:
const getData = async (url) => {
// would probably use const instead of let
const response = await fetch(url);
if (response.status === 401 || response.status === 403) {
navigate('/login');
return;
}
return response.json();
}
return { getData }
And now in the component you can grab getData from useGetData and use it however you want; the auth guard logic will be handled for us in the hook, no matter which component we use it in.
Hooks are JavaScript functions, but you need to follow two rules when using them.
Don’t call Hooks inside loops, conditions, or nested functions.
Don’t call Hooks from regular JavaScript functions.
So if you want to use hook inside a function, change that function into hook

Calling async method in ngInit not working in Angular 14 [duplicate]

I’m currently evaluating the pros ‘n’ cons of replacing Angular’s resp. RxJS’ Observable with plain Promise so that I can use async and await and get a more intuitive code style.
One of our typical scenarios: Load some data within ngOnInit. Using Observables, we do:
ngOnInit () {
this.service.getData().subscribe(data => {
this.data = this.modifyMyData(data);
});
}
When I return a Promise from getData() instead, and use async and await, it becomes:
async ngOnInit () {
const data = await this.service.getData();
this.data = this.modifyMyData(data);
}
Now, obviously, Angular will not “know”, that ngOnInit has become async. I feel that this is not a problem: My app still works as before. But when I look at the OnInit interface, the function is obviously not declared in such a way which would suggest that it can be declared async:
ngOnInit(): void;
So -- bottom line: Is it reasonable what I’m doing here? Or will I run into any unforseen problems?
It is no different than what you had before. ngOnInit will return a Promise and the caller will ignore that promise. This means that the caller will not wait for everything in your method to finish before it proceeds. In this specific case it means the view will finish being configured and the view may be launched before this.data is set.
That is the same situation you had before. The caller would not wait for your subscriptions to finish and would possibly launch the app before this.data had been populated. If your view is relying on data then you likely have some kind of ngIf setup to prevent you from accessing it.
I personally don't see it as awkward or a bad practice as long as you're aware of the implications. However, the ngIf can be tedious (they would be needed in either way). I have personally moved to using route resolvers where it makes sense so I can avoid this situation. The data is loaded before the route finishes navigating and I can know the data is available before the view is ever loaded.
Now, obviously, Angular will not “know”, that ngOnInit has become async. I feel that this is not a problem: My app still works as before.
Semantically it will compile fine and run as expected, but the convenience of writing async / wait comes at a cost of error handling, and I think it should be avoid.
Let's look at what happens.
What happens when a promise is rejected:
public ngOnInit() {
const p = new Promise((resolver, reject) => reject(-1));
}
The above generates the following stack trace:
core.js:6014 ERROR Error: Uncaught (in promise): -1
at resolvePromise (zone-evergreen.js:797) [angular]
at :4200/polyfills.js:3942:17 [angular]
at new ZoneAwarePromise (zone-evergreen.js:876) [angular]
at ExampleComponent.ngOnInit (example.component.ts:44) [angular]
.....
We can clearly see that the unhandled error was triggered by a ngOnInit and also see which source code file to find the offending line of code.
What happens when we use async/wait that is reject:
public async ngOnInit() {
const p = await new Promise((resolver, reject) => reject());
}
The above generates the following stack trace:
core.js:6014 ERROR Error: Uncaught (in promise):
at resolvePromise (zone-evergreen.js:797) [angular]
at :4200/polyfills.js:3942:17 [angular]
at rejected (tslib.es6.js:71) [angular]
at Object.onInvoke (core.js:39699) [angular]
at :4200/polyfills.js:4090:36 [angular]
at Object.onInvokeTask (core.js:39680) [angular]
at drainMicroTaskQueue (zone-evergreen.js:559) [<root>]
What happened? We have no clue, because the stack trace is outside of the component.
Still, you might be tempted to use promises and just avoid using async / wait. So let's see what happens if a promise is rejected after a setTimeout().
public ngOnInit() {
const p = new Promise((resolver, reject) => {
setTimeout(() => reject(), 1000);
});
}
We will get the following stack trace:
core.js:6014 ERROR Error: Uncaught (in promise): [object Undefined]
at resolvePromise (zone-evergreen.js:797) [angular]
at :4200/polyfills.js:3942:17 [angular]
at :4200/app-module.js:21450:30 [angular]
at Object.onInvokeTask (core.js:39680) [angular]
at timer (zone-evergreen.js:2650) [<root>]
Again, we've lost context here and don't know where to go to fix the bug.
Observables suffer from the same side effects of error handling, but generally the error messages are of better quality. If someone uses throwError(new Error()) the Error object will contain a stack trace, and if you're using the HttpModule the Error object is usually a Http response object that tells you about the request.
So the moral of the story here: Catch your errors, use observables when you can and don't use async ngOnInit(), because it will come back to haunt you as a difficult bug to find and fix.
I wonder what are the downsides of an immediately invoked function expression :
ngOnInit () {
(async () => {
const data = await this.service.getData();
this.data = this.modifyMyData(data);
})();
}
It is the only way I can imagine to make it work without declaring ngOnInit() as an async function
I used try catch inside the ngOnInit():
async ngOnInit() {
try {
const user = await userService.getUser();
} catch (error) {
console.error(error);
}
}
Then you get a more descriptive error and you can find where the bug is
ngOnInit does NOT wait for the promise to complete. You can make it an async function if you feel like using await like so:
import { take } from 'rxjs/operators';
async ngOnInit(): Promise<any> {
const data = await firstValueFrom(this.service.getData());
this.data = this.modifyMyData(data);
}
However, if you're using ngOnInit instead of the constructor to wait for a function to complete, you're basically doing the equivalent of this:
import { take } from 'rxjs/operators';
constructor() {
firstValueFrom(this.service.getData())
.then((data => {;
this.data = this.modifyMyData(data);
});
}
It will run the async function, but it WILL NOT wait for it to complete. If you notice sometimes it completes and sometimes it doesn't, it really just depends on the timing of your function.
Using the ideas from this post, you can basically run outside zone.js. NgZone does not include scheduleMacroTask, but zone.js is imported already into angular.
Solution
import { isObservable, Observable } from 'rxjs';
import { take } from 'rxjs/operators';
declare const Zone: any;
async waitFor<T>(prom: Promise<T> | Observable<T>): Promise<T> {
if (isObservable(prom)) {
prom = firstValueFrom(prom);
}
const macroTask = Zone.current
.scheduleMacroTask(
`WAITFOR-${Math.random()}`,
() => { },
{},
() => { }
);
return prom.then((p: T) => {
macroTask.invoke();
return p;
});
}
I personally put this function in my core.module.ts, although you can put it anywhere.
Use it like so:
constructor(private cm: CoreModule) {
const p = this.service.getData();
this.post = this.cm.waitFor(p);
}
You could also check for isBrowser to keep your observable, or wait for results.
Conversely, you could also import angular-zen and use it like in this post, although you will be importing more than you need.
J
UPDATE: 2/26/22 - Now uses firstValueFrom since .toPromise() is depreciated.
You can use rxjs function of.
of(this.service.getData());
Converts the promise to an observable sequence.
The correct answer to this question is to use Resolver feature of Angular. With Resolve, your component will not be rendered until you tell it to render.
From docs:
Interface that classes can implement to be a data provider. A data
provider class can be used with the router to resolve data during
navigation. The interface defines a resolve() method that is invoked
when the navigation starts. The router waits for the data to be
resolved before the route is finally activated.
I would do this a bit differently, you don't show your html template, but I'm assuming you're doing something in there with data, i.e.
<p> {{ data.Name }}</p> <!-- or whatever -->
Is there a reason you're not using the async pipe?, i.e.
<p> {{ (data$ | async).Name }}</p>
or
<p *ngIf="(data$ | async) as data"> {{ data.name }} </p>
and in your ngOnInit:
data$: Observable<any>; //change to your data structure
ngOnInit () {
this.data$ = this.service.getData().pipe(
map(data => this.modifyMyData(data))
);
}

How to call React component function from Electron main.js using contextBridge

As the title states, I would like to call a function defined in one of my React components from the Electron main.js file. I know how to do it the other way around, where for example the user clicks a button and it calls a function in main.js and then returns to the React component. I need it to be the opposite, where main.js calls the React function and then returns the result to main.
I cannot simply have all the functionality in main.js since I cannot pass DOM objects to main from React (it has resulted in the error "object cannot be cloned"). So I figure it shouldn't be too hard to just send the request to React, have the React side of my app do the stuff, and then return the resulting string back to main.
I have found a number of other similar posts here where various solutions are offered, but none specifically for this exact problem. #1 exposes ipcRenderer which is a bad security practice I can't afford. #2 and #3 only explain it for vanilla Javascript, not React components. And while #4 does deal with React, it does not deal with Electron, and it is designed for React classes (I'm using functional components).
My resulting attempt looks like this mess:
const [mounted, setMounted] = useState(false)
if(!mounted){
window.reactFunction = myFunction;
}
useEffect(() =>{
setMounted(true)
},[])
window.electron.receive("fromMain", async () => {
return myFunction();
});
async function myFunction()
{
return "result";
}
However, the function instead gets called during rendering and is not called by the contextBridge. Here is my preload.js:
contextBridge.exposeInMainWorld('electron', {
receive: async (channel, func) => {
let validChannels = ["fromMain"];
if (validChannels.includes(channel)) {
return await ipcRenderer.on("fromMain", (event, ...args) => func(...args));
}
}
});
For some reason the preload.js script is not executed. And here is main.js:
const result = window.webContents.send('fromMain');
I have also found this discussion where it is made clear the security issues associated with calling ipcRenderer directly and so I would like to use contextBridge (which I'm already using for calling main functions from React anyway). It's also where my preload.js code comes from. But that example doesn't use React either.
Is this even possible? Or am I supposed to go about this problem a different way?
Looks like I made a typing mistake on this line:
return await ipcRenderer.on("fromMain", (event, ...args) => func(...args));
In this line I'm returning the function, so the function itself is not getting called. Remove the "return" and it will work. "Await" doesn't seem to make a difference.
However, once you do call the function, it can't actually return anything. So you need to make another call from renderer back to main, like this:
window.electron.receive("fromMain", async () => {
const returnValue = await myFunction();
window.electron.sendReturnValue(returnValue);
});
async function myFunction()
{
return "result";
}
Unless there is a still better way of doing it, but this solves my issue for now, and hopefully helps others.

Jest error -- Cannot read property 'get' of undefined

I have the configuration of a service inside a component in React and I am having problems with jest and testing-library, the app is working but the test is blocking.
import { appSetupConfig } from '.../myapp'
import theConfig from '.../config'
useEffect(() => {
const allowAppInstance = appSetupConfig();
allowAppInstance.get(theConfig).then((value) => {
if (value.something) {
Do Something;
}
...the rest of code
}, []);
This theConfig is an external file containing an object.
This is the error:
TypeError: Cannot read property 'get' of undefined
37 | const allowAppInstance = appSetupConfig();
38 |
> 39 | allowAppInstance.get(theConfig).then((value) => {
Is there any way to mock this get in jest's setup.js?
I don’t necessarily need to test this item yet, but I can’t proceed without it.
Yes, there is. So it would seem that you have called jest.mock('.../myapp') or similar at some point. In the mock object that Jest creates for the module, every mock function returns undefined. You need to mock a return value on appSetupConfig that is itself a mock object with the method(s) you need like get. Then get in turn needs to return a mock promise, and so on as deeply as needed. In your setup file, this would look like:
import { appSetupConfig } from '.../myapp'
...
jest.mock('.../myapp');
appSetupConfig.mockReturnValue({
get: jest.fn().mockResolvedValue({ something: jest.fn() }),
});
Your .then block will then be called in the test(s) with value set to undefined, but you can mock a different resolved value or a rejection of the promise for particular tests.

How can we implement componentWillUnmount using react hooks?

The method componentWillUnmount() is invoked immediately before a component is unmounted and destroyed. If we use useEffect with an empty array ([]) as the second argument and put our function in return statement it will be executed after the component is unmounted and even after another component will be mounted. This is done for performance reasons as far as I understand. In order not to delay rendering.
So the question is - how can we call some function using hooks before a component gets unmounted?
What I am trying to do is an application which saves user's input as he types (without submitting form). I use setInterval to save updated text every N seconds. And I need to force save updates before the component will unmount. I don't want to use prompt by react router before navigating. This is an electron application. I appreciate any thoughts or advice on how to implement such functionality.
Update
Unfortunately, Effects with Cleanup run after letting the browser paint. More details can be found here: So What About Cleanup?. It basically means that cleanup is run after a component is unmounted and it is not the same as executing code in componentWillUnmount(). I can clearly see the sequence of calls if I put console.log statements in the cleanup code and in another component. The question is whether we can execute some code before a component is unmounted using hooks.
Update2
As I can see I should better describe my use case. Let's imagine a theoretical app which holds its data in a Redux store. And we have two components with some forms. For simplicity, we don't have any backend or any async logic. We use only Redux store as data storage.
We don't want to update Redux store on every keystroke. So we keep actual values in the local component's state which we initialize with values from the store when a component mounts. We also create an effect which sets up a setInterval for 1s.
We have the following process. A User types something. Updates are stored in the local component state until our setInterval callback is called. The callback just puts data in the store (dispatches action). We put our callback in the useEffect return statement to force save to store when the component gets unmounted because we want to save data to store in this case as soon as possible.
The problem comes when a user types something in the first component and immediately goes to the second component (faster than 1s). Since the cleanup in our first component will be called after re-rendering, our store won't be updated before the second component gets mounted. And because of that, the second component will get outdated values to its local state.
If we put our callback in componentWillUnmount() it will be called before unmounting and the store will be updated before the next component mounts. So can we implement this using hooks?
componentWillUnmount can be simulated by returning a function inside the useEffect hook. The returned function will be called just before every rerendering of the component. Strictly speaking, this is the same thing but you should be able to simulate any behaviour you want using this.
useEffect(() => {
const unsubscribe = api.createSubscription()
return () => unsubscribe()
})
Update
The above will run every time there is a rerender. However, to simulate the behaviour only on mounting and unmounting (i.e. componentDidMount and componentWillUnmount). useEffect takes a second argument which needs to be an empty array.
useEffect(() => {
const unsubscribe = api.createSubscription()
return () => unsubscribe()
}, [])
See a more detailed explanation of the same question here.
Since the introduction of the useLayoutEffect hook, you can now do
useLayoutEffect(() => () => {
// Your code here.
}, [])
to simulate componentWillUnmount. This runs during unmount, but before the element has actually left the page.
The question here is how do you run code with hooks BEFORE unmount? The return function with hooks runs AFTER unmount and whilst that doesn’t make a difference for most use cases, their are some where it is a critical difference.
Having done a bit of investigation on this, I have come to the conclusion that currently hooks simply does not provide a direct alternative to componentWillUnmount. So if you have a use case that needs it, which is mainly for me at least, the integration of non-React libs, you just have to do it the old way and use a component.
Update: see the answer below about UseLayoutEffect() which looks like it may solve this issue.
I agree with Frank, but the code needs to look like this otherwise it will run only on the first render:
useLayoutEffect(() => {
return () => {
// Your code here.
}
}, [])
This is equivalent to ComponentWillUnmount
Similar to #pritam's answer, but with an abstracted code example. The whole idea of useRef is to allow you to keep track of the changes to the callback and not have a stale closure at the time of execution. Hence, the useEffect at the bottom can have an empty dependency array to ensure it only runs when the component unmounts. See the code demo.
Reusable hook:
type Noop = () => void;
const useComponentWillUnmount = (callback: Noop) => {
const mem = useRef<Noop>();
useEffect(() => {
mem.current = callback;
}, [callback]);
useEffect(() => {
return () => {
const func = mem.current as Noop;
func();
};
}, []);
};
After a bit of research, found that - you could still accomplish this. Bit tricky but should work.
You can make use of useRef and store the props to be used within a closure such as render useEffect return callback method
function Home(props) {
const val = React.useRef();
React.useEffect(
() => {
val.current = props;
},
[props]
);
React.useEffect(() => {
return () => {
console.log(props, val.current);
};
}, []);
return <div>Home</div>;
}
DEMO
However a better way is to pass on the second argument to useEffect so that the cleanup and initialisation happens on any change of desired props
React.useEffect(() => {
return () => {
console.log(props.current);
};
}, [props.current]);
I got in a unique situation where the useEffect(() => () => { ... }, []); answers did not work for me. This is because my component never got rendered — I was throwing an exception before I could register the useEffect hook.
function Component() {
useEffect(() => () => { console.log("Cleanup!"); }, []);
if (promise) throw promise;
if (error) throw error;
return <h1>Got value: {value}</h1>;
}
In the above example, by throwing a Promise<T> that tells react to suspend until the promise is resolved. However, once the promise is resolved, an error is thrown. Since the component never gets rendered and goes straight to an ErrorBoundary, the useEffect() hook is never registered!
If you're in a similar situation as myself, this little code may help:
To solve this, I modified my ErrorBoundary code to run a list of teardowns once it was recovered
export default class ErrorBoundary extends Component {
// ...
recover() {
runTeardowns();
// ...
}
// ...
}
Then, I created a useTeardown hook which would add teardowns that needed to be ran, or make use of useEffect if possible. You'll most likely need to modify it if you have nesting of error boundaries, but for my simple usecase, it worked wonderfully.
import React, { useEffect, useMemo } from "react";
const isDebugMode = import.meta.env.NODE_ENV === "development";
const teardowns: (() => void)[] = [];
export function runTeardowns() {
const wiped = teardowns.splice(0, teardowns.length);
for (const teardown of wiped) {
teardown();
}
}
type Teardown = { registered?: boolean; called?: boolean; pushed?: boolean } & (() => unknown);
/**
* Guarantees a function to run on teardown, even when errors occur.
*
* This is necessary because `useEffect` only runs when the component doesn't throw an error.
* If the component throws an error before anything renders, then `useEffect` won't register a
* cleanup handler to run. This hook **guarantees** that a function is called when the component ends.
*
* This works by telling `ErrorBoundary` that we have a function we would like to call on teardown.
* However, if we register a `useEffect` hook, then we don't tell `ErrorBoundary` that.
*/
export default function useTeardown(onTeardown: () => Teardown, deps: React.DependencyList) {
// We have state we need to maintain about our teardown that we need to persist
// to other layers of the application. To do that, we store state on the callback
// itself - but to do that, we need to guarantee that the callback is stable. We
// achieve this by memoizing the teardown function.
const teardown = useMemo(onTeardown, deps);
// Here, we register a `useEffect` hook to run. This will be the "happy path" for
// our teardown function, as if the component renders, we can let React guarantee
// us for the cleanup function to be ran.
useEffect(() => {
// If the effect gets called, that means we can rely on React to run our cleanup
// handler.
teardown.registered = true;
return () => {
if (isDebugMode) {
// We want to ensure that this impossible state is never reached. When the
// `runTeardowns` function is called, it should only be ran for teardowns
// that have not been able to be hook into `useEffect`.
if (teardown.called) throw new Error("teardown already called, but unregistering in useEffect");
}
teardown();
if (isDebugMode) {
// Because `teardown.registered` will already cover the case where the effect
// handler is in charge of running the teardown, this isn't necessary. However,
// this helps us prevent impossible states.
teardown.called = true;
}
};
}, deps);
// Here, we register the "sad path". If there is an exception immediately thrown,
// then the `useEffect` cleanup handler will never be ran.
//
// We rely on the behavior that our custom `ErrorBoundary` component will always
// be rendered in the event of errors. Thus, we expect that component to call
// `runTeardowns` whenever it deems it appropriate to run our teardowns.
// Because `useTeardown` will get called multiple times, we want to ensure we only
// register the teardown once.
if (!teardown.pushed) {
teardown.pushed = true;
teardowns.push(() => {
const useEffectWillCleanUpTeardown = teardown.registered;
if (!useEffectWillCleanUpTeardown) {
if (isDebugMode) {
// If the useEffect handler was already called, there should be no way to
// re-run this teardown. The only way this impossible state can be reached
// is if a teardown is called multiple times, which should not happen during
// normal execution.
const teardownAlreadyCalled = teardown.called;
if (teardownAlreadyCalled) throw new Error("teardown already called yet running it in runTeardowns");
}
teardown();
if (isDebugMode) {
// Notify that this teardown has been called - useful for ensuring that we
// cannot reach any impossible states.
teardown.called = true;
}
}
});
}
}
It does not matter wether the returned function from useEffect gets called before or after the component unmounted: You still have access to the states valuey through the closure:
const [input, setInput] = useState(() => Store.retrieveInput());
useEffect(() => {
return () => Store.storeInput(input); // < you can access "input" here, even if the component unmounted already
}, []);
If you don't manage the input in the components state, your whole structure is broken and should be changed to manage state at the right place. In your case, you should lift the shared input state of the components to the parent.
ReactJS docs on hooks specify this:
Effects may also optionally specify how to “clean up” after them by
returning a function.
So any function you return in your useEffect hook, will be executed when the component unmounts, as well as before re-running the effect due to a subsequent render.

Categories

Resources