Can't access Ref from useEffect using "this.refname.current" - javascript

I'm missing something simple here, I need to access a defined Ref within a useEffect . The debugger shows that this is undefined and therefore I can't do this.refOverlay.current;.
// Ref
const refOverlay = useRef(null);
// Sample useEffect that references it
useEffect(() => {
if (props.mode === 'new') {
let b = this; // Undefined
let a = this.refOverlay.current; // Error on this line
if (a) {
console.log('ok');
}
}
}, [props.mode]);
...
return (
{/* JSX Definition */}
<OverlayTrigger ref={refOverlay} trigger="click" rootClose placement="bottom" overlay={popoverApprover}>
TEST
</OverlayTrigger>
);

There's no such this in the function component.
const Title = () => {
const ref = useRef(null)
useEffect(() => {
if (ref.current) {
ref.current.WHATEVERYOUWANTHERE
}
}, [])
}
NOTE: couple of difference between class and function component
function component has no this, it's a function
function component is only the render function
to have state, you need useState
to have any persistent storage, you need hook

You have defined a functional based component. Which doesn't have the this
Simply remove the this keyword and you're fine.

Related

Too many re-renders - while trying to put props(if exists) in state

I am transfer an props from father component to child component.
On the child component I want to check if the father component is deliver the props,
If he does, i"m putting it on the state, If not I ignore it.
if(Object.keys(instituteObject).length > 0)
{
setInnerInstitute(instituteObject)
}
For some reason the setInnerInstitute() take me to infinite loop.
I don't know why is that happening and how to fix it.
getInstitutesById() - Is the api call to fetch the objects.
Father component(EditInstitute):
const EditInstitute = props => {
const {id} = props.match.params;
const [institute, setInstitute] = useState({})
useEffect(() => { //act like componentDidMount
getInstitutesById({id}).then((response) => {
setInstitute(response)
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
return (
<React.Fragment>
<InstituteForm instituteObject={institute.object}/>
</React.Fragment>
)
}
Child component(InstituteForm):
const InstituteForm = (props) => {
const {instituteObject = {}} = props // if not exist default value = {}
const [innerInstitute, setInnerInstitute] = useState({})
if (Object.keys(instituteObject).length > 0) // if exists update the state.
{
setInnerInstitute(instituteObject)
}
return (
<React.Fragment>
not yet.
</React.Fragment>
)
}
Thanks
I think the way you are changing your InstituteForm's state causing this error. You can try using the useEffect hook to change your innerInstitute based on instituteObject. That's why you need to also add instituteObject in the dependency array of that useEffect hook.
import { useEffect, useState } from "react"
const InstituteForm = (props) => {
const {instituteObject = {}} = props // if not exist default value = {}
const [innerInstitute, setInnerInstitute] = useState({})
useEffect(() => {
// this is be evoked only when instituteObject changes
if (Object.keys(instituteObject).length > 0){
setInnerInstitute(instituteObject)
}
}, [instituteObject])
return (
<React.Fragment>
not yet.
</React.Fragment>
)
}

ResizeObserver API doesn't get the updated state in React

I am using ResizeObserver to call a function when the screen is resized, but I need to get the updated value of a state within the observer in order to determine some conditions before the function gets invoked.
It's something like this:
let [test, setTest] = React.useState(true)
const callFunction = () => {
console.log('function invoked')
setTest(false) // => set 'test' to 'false', so 'callFunction' can't be invoked again by the observer
}
const observer = React.useRef(
new ResizeObserver(entries => {
console.log(test) // => It always has the initial value (true), so the function is always invoked
if (test === true) {
callFunction()
}
})
)
React.useEffect(() => {
const body = document.getElementsByTagName('BODY')[0]
observer.current.observe(body)
return () => observer.unobserve(body)
}, [])
Don't worry about the details or why I'm doing this, since my application is way more complex than this example.
I only need to know if is there a way to get the updated value within the observer. I've already spent a considerable time trying to figure this out, but I couldn't yet.
Any thoughts?
The problem is, you are defining new observer in each re render of the component, Move it inside useEffect will solve the problem. also you must change this observer.unobserve(body) to this observer..current.unobserve(body).
I have created this codesandbox to show you how to do it properly. this way you don't need external variable and you can use states safely.
import { useEffect, useState, useRef } from "react";
const MyComponent = () => {
const [state, setState] = useState(false);
const observer = useRef(null);
useEffect(() => {
observer.current = new ResizeObserver((entries) => {
console.log(state);
});
const body = document.getElementsByTagName("BODY")[0];
observer.current.observe(body);
return () => observer.current.unobserve(body);
}, []);
return (
<div>
<button onClick={() => setState(true)}>Click Me</button>
<div>{state.toString()}</div>
</div>
);
};
export default MyComponent;

Dynamic import of react hooks

Can we dynamically import hooks based on the value passed to the component?
For eg.
App.js
<BaseComponent isActive />
BaseComponent.js
if(props.isActive) {
// import useActive (custom Hook)
}
I donot want these(custom hook) files to be imported and increase the size of BaseComponent even when the props contain falsey values.
You can dynamically import hooks as it is just a function (using require), but you shouldn't because you can't use hooks inside conditions.
See Rules of Hooks
Only call Hooks at the top level. Don’t call Hooks inside loops, conditions, or nested functions.
If you want conditionally use a hook, use the condition in its implementation (look for example at skip option of useQuery hook from Apollo GraphQL Client).
const useActive = (isUsed) => {
if (isUsed) {
// logic
}
}
You should extract the logic inside the useActive hook and dynamically import it instead of dynamically importing the hook since you should not call Hooks inside loops, conditions, or nested functions., checkout Rules of Hooks:
Let's say your useActive hook was trying to update the document title (in real world, it has to be a big chunk of code that you would consider using dynamic import)
It might be implemented as below:
// useActive.js
import { useEffect } from "react";
export function useActive() {
useEffect(() => {
document.title = "(Active) Hello World!";
}, []);
}
And you tried to use it in the BaseComponent:
// BaseComponent.js
function BaseComponent({ isActive }) {
if (isActive) { // <-- call Hooks inside conditions ❌
import("./useActive").then(({ useActive }) => {
useActive();
});
}
return <p>Base</p>;
}
Here you violated the rule "don't call Hooks inside conditions" and will get an Invalid hook call. error.
So instead of dynamically import the hook, you can extract the logic inside the hook and dynamically import it:
// updateTitle.js
export function updateTitle() {
document.title = "(Active) Hello World!"
}
And you do the isActive check inside the hook:
// BaseComponent.js
function BaseComponent({ isActive }) {
useEffect(() => {
if (!isActive) return;
import("./updateTitle").then(({ updateTitle }) => {
updateTitle();
});
}, [isActive]);
return <p>Base</p>;
}
It works fine without violating any rules of hooks.
I have attached a CodeSandbox for you to play around:
You could create a Higher Order Component that fetches the hook and then passes it down as a prop to a wrapped component. By doing so the wrapped component can use the hook without breaking the rules of hooks, eg from the wrapped component's point of view, the reference to the hook never changes and the hook gets called everytime the wrapped component renders. Here is what the code would look like:
export function withDynamicHook(hookName, importFunc, Component) {
return (props) => {
const [hook, setHook] = useState();
useEffect(() => {
importFunc().then((mod) => setHook(() => mod[hookName]));
}, []);
if (!hook) {
return null;
}
const newProps = { ...props, [hookName]: hook };
return <Component {...newProps} />;
};
}
// example of a Component using it:
const MyComponent = ({useMyHook}) => {
let something = useMyHook();
console.log(something)
return <div>myHook returned something, see the console to inspect it </div>
}
const MyComponentWithHook = withDynamicHook('useMyHook', () => import('module-containing-usemyhook'), MyComponent)
To whoever encountered it as well: You can't use Hooks inside dynamically imported components(however, apparently if you does not use hooks even the first example works):
instead of:
const useDynamicDemoImport = (name) => {
const [comp, setComp] = useState(null);
useEffect(() => {
let resolvedComp = false;
import(`#site/src/demos/${name}`)
.then((m) => {
if (!resolvedComp) {
resolvedComp = true;
setComp(m.default);
}
})
.catch(console.error);
return () => {
resolvedComp = true;
};
}, []);
return comp;
};
const DemoPreviewer: FC<DemoPreviewerProps> = (props) => {
comp = useDynamicDemoImport(props.name);
return (
<Paper sx={{ position: "relative" }}>
{comp}
</Paper>
);
};
export default DemoPreviewer
use React Lazy instead and render the component later
const useDynamicDemoImport = (name) => {
const Comp = React.lazy(() => import(`#site/src/demos/${name}`));
return comp;
};
const RootDemoPreviewer: FC<DemoPreviewerProps> = (props) => {
console.log("RootDemoPreviewer");
return (
<React.Suspense fallback={<div>Loading...</div>}>
<DemoPreviewer {...props} />
</React.Suspense>
);
};
const DemoPreviewer: FC<DemoPreviewerProps> = (props) => {
const Comp = useDynamicDemoImport(props.name);
return (
<Paper sx={{ position: "relative" }}>
<Comp />
</Paper>
);
};
export default RootDemoPreviewer

Questions about useCallback hook and anonymous function

When passing a callback function, especially when passing a parameterized function, I know that I should use the useCallback hook because the use of anonymous functions can adversely affect performance.
the example of anonymous function I said is like this.
import React, { useState } from 'react';
const Component = () => {
const [param, setParam] = useState('');
...
return (
...
<SomeComponent
onClick={() => setParam('parameter')}
{...others}
/>
);
}
In the process of converting an anonymous function to use this hook, I encountered an error saying 'Too many renders' or it didn't work properly.
But I don't know exactly in what situation and in what situation.
and I used useCallback like below.
import React, { useState, useCallback } from 'react';
const Component = () => {
const [param, setParam] = useState('');
const handleClick = useCallback((params) => {
setParam(params);
},[]);
...
return (
...
<SomeComponent
onClick={handleClick('parameter')}
{...others}
/>
);
}
However, when using an anonymous function to return within useCallback, it also worked.
This means code like here. (Only the differences compared to the code above.)
const handleClick = useCallback((params) => {
return () => setParam(params);
},[]);
In this situation, I wonder if it's worse than just using an anonymous function inside the useCallback if I simply use an anonymous function instead of using this hook.
const handleClick = useCallback((params) => {
setParam(params);
},[]);
...
return (
...
<SomeComponent
onClick={handleClick('parameter')}
{...others}
/>
);
in the above code during first render, at this statement "onClick={handleClick('parameter')}" handleClick function is called with a string named "parameter". since handleClick has setParam("parameter"), it will update state. updating state will cause re-render which will again come to same statement "onClick={handleClick('parameter')}" causing infinte loop.
following code you added later works because you are not updating state, instead returning a function, which acts as onclick handler.
const handleClick = useCallback((params) => {
return () => setParam(params);
},[]);
better way of doing this shoud be as follwing,
import React, { useState, useCallback } from 'react';
const Component = () => {
const [param, setParam] = useState('');
const handleClick = useCallback((params) => {
setParam(params);
},[]);
...
return (
...
<SomeComponent
onClick={handleClick}
{...others}
/>
);
}
coming back to your question , comparing performance depends on the other function definitions and render times of child compoents inside return function inside your Compoenent.
let's say you have another onclickHanldier named 'anotherHandleClick' inside your app.
then your component looks like this
const Component = () => {
const [param, setParam] = useState('');
const [anotherParam, setAnotherParam] = useState('');
const handleClick = (params) => {
setParam(params);
};
const anotherHandleClick =(params) => {
setAnotherParam(params);
};
...
return (
...
<SomeComponent
onClick={handleClick('parameter')}
{...others}
/>
<SomeComponent
onClick={antherHandleClick('parameter')}
{...others}
/>
);
}
in the above component when any one of "SomeCompoenent" clicked entiere "Component" re-renders, so the handler functions are newly defined.and when both 's does referential equality check on onclick handler functions, they believe it is new handler function which casues them to render both.
in that case it is better to use useCallBack hook as following,
const Component = () => {
const [param, setParam] = useState('');
const [anotherParam, setAnotherParam] = useState('');
const handleClick = useCallback((params) => {
setParam(params);
},[]);
const anotherHandleClick = useCallback((params) => {
setAnotherParam(params);
},[]);
...
return (
...
<SomeComponent
onClick={handleClick('parameter')}
{...others}
/>
<SomeComponent
onClick={antherHandleClick('parameter')}
{...others}
/>
);
}
in the above code when any one is clicked , state changes. then when rendering, useCallback make sure that onclick handler refernces didn't change. so having dependency of onclick handlers won't be rerendered.
so final thought is It's creating a function on each render in both cases. the second (because it'e wrapped in a useCallback) will return the memoized function created on the initial render
when to use useMemo or useCallback refer this
In your code:
const handleClick = useCallback((params) => {
setParam(params);
},[]);
You should pass the parameters into the second argment of useCallback like so:
const handleClick = useCallback((params) => {
setParam(params);
},[setParam,param, SETPARAMACTUALVALUE]);
// Change SETPARAMACTUALVALUE to whatever the variable name is for the `setParam` hook
useCallback hook usage will be better if you want to save the function until hook's dependency changed. It gives you better performance because the hook remember internal function.

Wrong React hooks behaviour with event listener

I'm playing around with React Hooks and am facing a problem.
It shows the wrong state when I'm trying to console log it using a button handled by event listener.
CodeSandbox: https://codesandbox.io/s/lrxw1wr97m
Click on 'Add card' button 2 times
In first card, click on Button1 and see in console that there are 2 cards in state (correct behaviour)
In first card, click on Button2 (handled by event listener) and see in console that there is only 1 card in state (wrong behaviour)
Why does it show the wrong state?
In first card, Button2 should display 2 cards in the console. Any ideas?
const { useState, useContext, useRef, useEffect } = React;
const CardsContext = React.createContext();
const CardsProvider = props => {
const [cards, setCards] = useState([]);
const addCard = () => {
const id = cards.length;
setCards([...cards, { id: id, json: {} }]);
};
const handleCardClick = id => console.log(cards);
const handleButtonClick = id => console.log(cards);
return (
<CardsContext.Provider
value={{ cards, addCard, handleCardClick, handleButtonClick }}
>
{props.children}
</CardsContext.Provider>
);
};
function App() {
const { cards, addCard, handleCardClick, handleButtonClick } = useContext(
CardsContext
);
return (
<div className="App">
<button onClick={addCard}>Add card</button>
{cards.map((card, index) => (
<Card
key={card.id}
id={card.id}
handleCardClick={() => handleCardClick(card.id)}
handleButtonClick={() => handleButtonClick(card.id)}
/>
))}
</div>
);
}
function Card(props) {
const ref = useRef();
useEffect(() => {
ref.current.addEventListener("click", props.handleCardClick);
return () => {
ref.current.removeEventListener("click", props.handleCardClick);
};
}, []);
return (
<div className="card">
Card {props.id}
<div>
<button onClick={props.handleButtonClick}>Button1</button>
<button ref={node => (ref.current = node)}>Button2</button>
</div>
</div>
);
}
ReactDOM.render(
<CardsProvider>
<App />
</CardsProvider>,
document.getElementById("root")
);
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id='root'></div>
I am using React 16.7.0-alpha.0 and Chrome 70.0.3538.110
BTW, if I rewrite the CardsProvider using a сlass, the problem is gone.
CodeSandbox using class: https://codesandbox.io/s/w2nn3mq9vl
This is a common problem for functional components that use the useState hook. The same concerns are applicable to any callback functions where useState state is used, e.g. setTimeout or setInterval timer functions.
Event handlers are treated differently in CardsProvider and Card components.
handleCardClick and handleButtonClick used in the CardsProvider functional component are defined in its scope. There are new functions each time it runs, they refer to cards state that was obtained at the moment when they were defined. Event handlers are re-registered each time the CardsProvider component is rendered.
handleCardClick used in the Card functional component is received as a prop and registered once on component mount with useEffect. It's the same function during the entire component lifespan and refers to stale state that was fresh at the time when the handleCardClick function was defined the first time. handleButtonClick is received as a prop and re-registered on each Card render, it's a new function each time and refers to fresh state.
Mutable state
A common approach that addresses this problem is to use useRef instead of useState. A ref is basically a recipe that provides a mutable object that can be passed by reference:
const ref = useRef(0);
function eventListener() {
ref.current++;
}
In this case a component should be re-rendered on a state update like it's expected from useState, refs aren't applicable.
It's possible to keep state updates and mutable state separately but forceUpdate is considered an anti-pattern in both class and function components (listed for reference only):
const useForceUpdate = () => {
const [, setState] = useState();
return () => setState({});
}
const ref = useRef(0);
const forceUpdate = useForceUpdate();
function eventListener() {
ref.current++;
forceUpdate();
}
State updater function
One solution is to use a state updater function that receives fresh state instead of stale state from the enclosing scope:
function eventListener() {
// doesn't matter how often the listener is registered
setState(freshState => freshState + 1);
}
In this case a state is needed for synchronous side effects like console.log, a workaround is to return the same state to prevent an update.
function eventListener() {
setState(freshState => {
console.log(freshState);
return freshState;
});
}
useEffect(() => {
// register eventListener once
return () => {
// unregister eventListener once
};
}, []);
This doesn't work well with asynchronous side effects, notably async functions.
Manual event listener re-registration
Another solution is to re-register the event listener every time, so a callback always gets fresh state from the enclosing scope:
function eventListener() {
console.log(state);
}
useEffect(() => {
// register eventListener on each state update
return () => {
// unregister eventListener
};
}, [state]);
Built-in event handling
Unless the event listener is registered on document, window or other event targets that are outside of the scope of the current component, React's own DOM event handling has to be used where possible, this eliminates the need for useEffect:
<button onClick={eventListener} />
In the last case the event listener can be additionally memoized with useMemo or useCallback to prevent unnecessary re-renders when it's passed as a prop:
const eventListener = useCallback(() => {
console.log(state);
}, [state]);
Previous edition of this answer suggested to use mutable state that was applicable to initial useState hook implementation in React 16.7.0-alpha version but isn't workable in final React 16.8 implementation. useState currently supports only immutable state.*
A much cleaner way to work around this is to create a hook I call useStateRef
function useStateRef(initialValue) {
const [value, setValue] = useState(initialValue);
const ref = useRef(value);
useEffect(() => {
ref.current = value;
}, [value]);
return [value, setValue, ref];
}
You can now use the ref as a reference to the state value.
Short answer for me was that useState has a simple solution for this:
function Example() {
const [state, setState] = useState(initialState);
function update(updates) {
// this might be stale
setState({...state, ...updates});
// but you can pass setState a function instead
setState(currentState => ({...currentState, ...updates}));
}
//...
}
Short answer for me
this WILL NOT not trigger re-render ever time myvar changes.
const [myvar, setMyvar] = useState('')
useEffect(() => {
setMyvar('foo')
}, []);
This WILL trigger render -> putting myvar in []
const [myvar, setMyvar] = useState('')
useEffect(() => {
setMyvar('foo')
}, [myvar]);
Check the console and you'll get the answer:
React Hook useEffect has a missing dependency: 'props.handleCardClick'. Either include it or remove the dependency array. (react-hooks/exhaustive-deps)
Just add props.handleCardClick to the array of dependencies and it will work correctly.
This way your callback will have updated state values always ;)
// registers an event listener to component parent
React.useEffect(() => {
const parentNode = elementRef.current.parentNode
parentNode.addEventListener('mouseleave', handleAutoClose)
return () => {
parentNode.removeEventListener('mouseleave', handleAutoClose)
}
}, [handleAutoClose])
To build off of Moses Gitau's great answer, if you are developing in Typescript, to resolve type errors make the hook function generic:
function useStateRef<T>(initialValue: T | (() => T)):
[T, React.Dispatch<React.SetStateAction<T>>, React.MutableRefObject<T>] {
const [value, setValue] = React.useState(initialValue);
const ref = React.useRef(value);
React.useEffect(() => {
ref.current = value;
}, [value]);
return [value, setValue, ref];
}
Starting from the answer of #Moses Gitau, I'm using a sligthly different one that doesn't give access to a "delayed" version of the value (which is an issue for me) and is a bit more minimalist:
import { useState, useRef } from 'react';
function useStateRef(initialValue) {
const [, setValueState] = useState(initialValue);
const ref = useRef(initialValue);
const setValue = (val) => {
ref.current = val;
setValueState(val); // to trigger the refresh
};
const getValue = (val) => {
return ref.current;
};
return [getValue , setValue];
}
export default useStateRef;
This is what I'm using
Example of usage :
const [getValue , setValue] = useStateRef(0);
const listener = (event) => {
setValue(getValue() + 1);
};
useEffect(() => {
window.addEventListener('keyup', listener);
return () => {
window.removeEventListener('keyup', listener);
};
}, []);
Edit : It now gives getValue and not the reference itself. I find it better to keep things more encapsulated in that case.
after changing the following line in the index.js file the button2 works well:
useEffect(() => {
ref.current.addEventListener("click", props.handleCardClick);
return () => {
ref.current.removeEventListener("click", props.handleCardClick);
};
- }, []);
+ });
you should not use [] as 2nd argument useEffect unless you want it to run once.
more details: https://reactjs.org/docs/hooks-effect.html

Categories

Resources