Is it possible to use objects declared in useeffect hook? - reactjs

I want to use object declared in useEffect hook, like
function App(){
useEffect(() => {
let myObject = .....
}
myObject.myFunction()
}
but IDE gives me an error that myObject is not defined...
I assume that it is not a good way to lead re-rendering objects declared in useEffect hook, but anyway, is there any way to use objects declared in useEffect hook?

I don't think so, because it's in different scope.
it would be ok to use state instead, because when useEffect call and object change, you wont get new value in object
like this :
function App(){
const [myObject,setMyObject] = useState()
useEffect(() => {
setMyObject(...)
},[])
myObject?.myFunction()
}

First your code is wrong. It's not useeffect it's useEffect, so:
useeffect(() => {
let myObject = .....
}
should be:
useEffect(() => {
let myObject = .....
}
Second you would use useState in this scenario:
const [ myObject, setMyObject ] = useState()
useEffect(() => {
setMyObject(...)
}, [])
myObject?.myFunction()
Third make sure you're importing them correctly with something like:
import React, { useState, useEffect } from 'react'
function So75221887() {
const [ myObject, setMyObject ] = useState()
useEffect(() => {
setMyObject(...)
}, [])
myObject?.myFunction()
}
export default So75221887

Related

Stale custom hook state property on callback

I have a custom hook in my React app that exposes a function (hookFn) to calculate a value. Once the value has been updated (state change, triggering useEffect), the hook alerts the app via a callback function. Here's the issue: in my callback function, I want to be able to access the value via hook.value, but it seems to be stale! Even though I know the value state has been updated!
Codesandbox: https://codesandbox.io/s/stoic-payne-bwp6j5?file=/src/App.js:0-910
import { useEffect, useRef, useState } from "react";
export default function App() {
const hook = useCustomHook();
useEffect(() => {
hook.hookFn(hookCallback);
}, []);
function hookCallback(value) {
console.log({givenValue: value, hookValue: hook.value});
}
return "See console for output";
}
function useCustomHook() {
const callbackRef = useRef(null);
const [value, setValue] = useState("initial value");
useEffect(() => {
if (callbackRef.current) {
callbackRef.current(value);
}
}, [value]);
function hookFn(callbackFn) {
callbackRef.current = callbackFn;
setValue("value set in hookFn");
}
return { hookFn, value };
}
FYI: in my actual app, the hook is for searching, which may call the callback function multiple times as more search results become available.
Is there any way to ensure hook.value will be valid? Or is it bad practice for a hook to expose a state variable in general?
It turns out hook.value is stale because hook is stale when I access it from hookCallback. Each time there is a state change within my custom hook, useCustomHook will generate a new object.
The complex solution, then, is to to create a ref for hook and keep it up to date in useEffect. But then I have to make sure I wait for that useEffect to run before accessing hookRef.current.value... Here's my attempt to make this work: https://codesandbox.io/s/dazzling-shirley-0r7k47?file=/src/App.js
However, a better solution: don't mix React states and manual callbacks. Instead, just watch for state changes in a useEffect, like so:
import { useEffect, useState } from "react";
export default function App() {
const hook = useCustomHook();
useEffect(() => {
hook.hookFn();
}, []);
useEffect(() => {
if (hook.value) console.log({ hookValue: hook.value });
}, [hook.value]);
return "See console for output";
}
function useCustomHook() {
const [value, setValue] = useState("initial value");
function hookFn(callbackFn) {
setValue("value set in hookFn");
}
return { hookFn, value };
}
Notice the code is simplified, and there's no need for concern about states being out-of-sync.
I think you have pretty much answered you own question. Alternatively, you could pass your callback function as input to your custom hook.
import { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const hook = useCustomHook(hookCallback);
useEffect(() => {
hook.setNewValue();
},[])
function hookCallback(value) {
console.log({
givenValue: value,
hookValue: hook.value, // Why is this stale??
areIdentical: value === hook.value // Should be true!!!
});
}
return <h1>See console for output</h1>;
}
function useCustomHook(callback) {
const [value, setValue] = useState("initial value");
useEffect(() => {
callback(value);
}, [value]);
function setNewValue(callbackFn) {
setValue("value set in hookFn");
setTimeout(() => {
setValue("value set in setTimeout");
}, 100);
}
return { setNewValue, value };
}

Why useEffect does not behave like what i expect?

I was wondering Why this isn't an infinite loop?
I set the state inside of the useEffect hook and that should re-render the component and useEffect will be ran again and again and agian...
why this isn't what i expected?
import React, { useState, useEffect } from "react";
import axios from "axios";
const App = () => {
const [data, setData] = useState();
useEffect(() => {
axios.get("https://jsonplaceholder.typicode.com/comments").then((r) => {
console.log("Checking...");
setData(r.data[0]);
});
});
return (
<div>
<h1>{data}</h1>
</div>
);
};
export default App;
useEffect also accepts an array of dependencies i.e., useEffect will get executed when any values inside the array changes. So, if you want useEffect to be called whenever data changes, do this:
useEffect(() => {
axios.get("https://jsonplaceholder.typicode.com/comments").then((r) => {
console.log("Hello world");
setData(r.data[0]);
});
}, [data]);
The useEffect hook needs a second parameter, the dependency array.
When this dependency array is empty it will have the same behavior as the componentDidMount
If you add some dependencies to your array as data this useEffect will be executed every time that the data state changes.
So, if you want to fetch the data when the component loads you must do something like this.
useEffect(() => {
axios.get("https://jsonplaceholder.typicode.com/comments").then((r) => {
console.log("Hello world");
setData(r.data[0]);
});
}, []);

Cleanup custom event from ref in react

I have a custom element, that dispatch a custom event in react.
Since there is no way to bind the event simply, I need to create a eventListener manually and remove it.
I tried :
export function App() {
const textRef = useRef<DemoTextElement>(null);
let eventRef;
const logEvent = (e) => {
console.log(e)
}
useEffect(() => {
if(textRef.current) {
eventRef = textRef.current;
textRef.current.addEventListener('onCustomEvent', logEvent)
}
return () => {
if(eventRef) {
eventRef.removeEventListener('onCustomEvent', logEvent)
}
}
}, [])
return (
<div className={styles.app}>
<demo-text ref={textRef}/>
</div>
);
}
but I get prompted that
Assignments to the 'eventRef' variable from inside React Hook useEffect will be lost after each render. To preserve the value over time, store it in a useRef Hook and keep the mutable value in the '.current' property. Otherwise, you can move this variable directly inside useEffect.
but I am, already using useRef hook, so I don't exactly know how to fix this... Cause I can't add useRef hook inside useEffect hook.
The eventRef variable will be re-declared for each rendering. Just move it inside useEffect hooks and do the assignment operation.
import React, { useEffect, useRef } from 'react';
export default function Test() {
const textRef = useRef<HTMLDivElement>(null);
const logEvent = (e) => {
console.log(e);
};
useEffect(() => {
const eventRef = textRef.current;
if (eventRef) {
eventRef.addEventListener('onCustomEvent', logEvent);
}
return () => {
if (eventRef) {
eventRef.removeEventListener('onCustomEvent', logEvent);
}
};
}, []);
return <div ref={textRef}>123</div>;
}

Export function from inside a React function

I have a function that handles React Native location. For demonstration:
const useLocation = () => {
const [fetchingLocation, setFetchingLocation] = useState(true);
...
const changeSystemPermissions = useCallback(() => {...});
useEffect(() => {
//does many things
}, [...])
}
I need to have the function changeSystemPermissions inside useLocation as it uses the state.
I realize that I can export the changeSystemPermissions function as a const with a return [changeSystemPermissions, ...] and then import it in another component with:
const [
changeSystemPermissions,
...
] = useLocation();
However, it will ALSO run the useEffect function. I do want it to run once, but I need to access changeSystemPermissions in several other components and I don't want the useEffect to run multiple times.
I was thinking I will just take out the changeSystemPermissions function outside of useLocation, but it needs to use the state. I suppose I COULD pass the state vars into the changeSystemPermissions when it is outside useLocation, but that would be verbose and ugly.
How can I export changeSystemPermissions and just that function without having to import the whole useLocation function?
Can you move the useEffect to the component one ?
const useLocation = () => {
const [fetchingLocation, setFetchingLocation] = useState(true);
const changeSystemPermissions = useCallback(() => {
...
});
const funcToExecute = useCallback(() => {
....
}, []);
return { changeSystemPermissions, funcToExecute }
}
And put it in the component :
const {
changeSystemPermissions,
funcToExecute,
} = useLocation();
useEffect(() => {
funcToExecute()
}, [...])
Also, if you really need the useEffect to be in the custom hook,
maybe you can add a param to this hook.
const useLocation = (shouldTriggerEffect) => {
const [fetchingLocation, setFetchingLocation] = useState(true);
const changeSystemPermissions = useCallback(() => {
...
});
useEffect(() => {
if (shouldTriggerEffect) {
...
}
}, [shouldTriggerEffect])
return { changeSystemPermissions, funcToExecute }
}
And then in the component,
const {
changeSystemPermissions,
} = useLocation(false);
Tell me if I misunderstood something or if it helps :)
When ever you call a hook inside a React functional component, it will create a new state for that hook and not sharing among components. But there is a library which could help you achieve that:
https://github.com/betula/use-between
You could follow example to use this library or maybe just read the code and utilize the approach for your case to share the hook state between components.

I am trying to use callback in hook but can not get latest context value in the callback

const Demo = () => {
const { name } = useContext(AppContext);
function emiterCallback(val) {
console.log('value==', name);
if (name !== val) {
setContextState({ name: val });
}
}
useEffect(() => {
window.eventEmitter.on('CHANGED', emiterCallback);
return () => {
window.eventEmitter.removeListener('CHANGED', emiterCallback);
};
}, []);
}
in class component
this.emiterCallback = this.emiterCallback.bind(this) can solve my question, but how to use it in hook ?
The problem you have here is due to the fact that useEffect with an empty array dependency only runs once - when the component mounts. This means that the emiterCallback it assigns as the event function is the very first one that's made on the first render. Since you just declare emiterCallback in the body of the function, it gets remade every single re-render, so after a single re-render, it will be a different one to the event one you assigned when the component mounted. Try something like this:
import React, { useCallback, useContext, useEffect } from 'react';
...
const Demo = () => {
const { name } = useContext(AppContext);
// Assign it to a memoized function that will recalculate as needed when the context value changes
const emiterCallback = useCallback((val) => {
console.log('value==', name);
if (name !== val) {
setContextState({ name: val });
}
}, [name]);
// Adding the function as a dependency means the .on function should be updated as needed
useEffect(() => {
window.eventEmitter.on('CHANGED', emiterCallback);
return () => {
window.eventEmitter.removeListener('CHANGED', emiterCallback);
};
}, [emiterCallback]);
}
This code isn't tested but you get the idea
use useCallback to memorize the effect no need for bind since there is no this as it is not a class,
Here read more about it -
How can I bind function with hooks in React?

Resources