can i ask using useEffect inside onFocus Event? - reactjs

const [showSearch, setShowSearch] = useState(false);
const [inputHover, setInputHover] = useState(false);
...
<div className={`search ${showSearch ? "show-search" : ""} `}>
<button
onFocus={
setShowSearch(true)
}
onBlur={() => {
if (!inputHover) {
return setShowSearch(false);
}
}}
>
First I'm using just useState but
[Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.]
this error occurred
so I wrapped it inside useEffect then
[Warning: Expected onFocus listener to be a function, instead got a value of object type.]
I find this error children component receives props but just direct push event function
so.. finally upside code is complete...
but this code does not work.
help me, please

The error Expected onFocus listener to be a function, instead got a value of object type occurred because you tried to pass an object as an event handler instead of a function. You should pass a function as the event handler to avoid this error. So onFocus={ ()=> setShowSearch(true) } is the right way to do it.
[Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.] appeared because you called setShowSearch(true) in onFocus handler and every time when button rendered setShowSearch(true) executed and changed the state and it triggered another rerender and and setShowSearch(true) executed again and so it went on endlessly. When you put it in a callback form like onFocus={ ()=> setShowSearch(true)} it only gets executed when you focus the button

onFocus={ ()=> setShowSearch(true) }
i fixed right now.
but i didn't know why this code work no error.
who tell me why that code dosen't work and this code work?

Related

Value in Event Listener attached through useEffect is always one step behind React State

I have a queryTerm variable as a dependency to a useEffect hook. I also register a handleKeyPress event listener in the same hook.
I am facing an issue whereby when the queryTerm dependency changes, the queryTerm value in the handleKeyPress listener is always one step behind the new queryTerm value.
How would I go about synchronising the two?
import React, { useState, useEffect } from 'react'
const Test = () => {
const [queryTerm, setQueryTerm] = useState('')
const handleChange = (event) => {
setQueryTerm(event.target.value)
}
useEffect(() => {
console.log({ outside: queryTerm })
const handleKeyPress = (event) => console.log({ inside: queryTerm })
window.addEventListener('keydown', handleKeyPress)
return () => window.removeEventListener('keydown', handleKeyPress)
}, [queryTerm])
return (
<div className='App'>
<input onChange={handleChange} />
</div>
)
}
export default Test
There are actually 3 issues here:
The "keydown" and "change" events occur in a certain order, so if you log something during the 1st, it will surely not have been affected yet by the 2nd
Even if you change the state and log it within the same event listener, the log will not be affected by the state change yet, because the latter will be effective on next render only; see The useState set method is not reflecting a change immediately
When building a callback that uses some state in its body, it actually "embeds" a stale value of that state as its closure; think about the state variable as being a different reference every render (the previous link also explains this); workarounds include leveraging the useRef hook, but it depends on each specific situation
In your case, it is unclear why you need to use your state on "keydown" (and why it must be attached to window rather than to the <input/>), and expect it to reflect a state change that occurs on "change" event?
It might be a contrived example, but as is, you could simply do:
const handleChange = (event) => {
setQueryTerm(event.target.value)
// 1. Do everything in the same event handler
// 2. and 3. Use directly the new value
// instead of reading the state that will
// change only on next render
console.log({ inside: event.target.value })
}
(This is effectively the same thing ghybs said, but i wandered off without hitting submit after typing it up; posting anyway in the hope that it still might provide some value.)
The keydown event fires before the input's onChange. So when you type a character into the input, the following occurs in this order:
handleKeyPress fires, logging the current (about-to-be-updated) queryTerm value. (inside)
handleChange fires, updating queryTerm with the new value.
Your effect runs, logging the new value (outside), and attaching a new handleKeyPress with the new value.
Let's go step by step what happens in your component
useEffect has this [queryTerm] dependency. That means in initial render and each time queryTerm changes the code inside useEffect will run. So in the initial render, this will run first
console.log({ outside: queryTerm })
you will see outside: "". also you will register handleKeyPress for keydown event. this will not run, it will be just registered.
write "a" inside the input element. Since we registered an event handler for keydown event and this event handler is synchronous it will run so you will see the current state
// meanwhiwle async setState is updating the state
inside:""
Because setState is async code, it will run after. When setState finishes its execution, queryTerm will be set to "a". Since you have [queryTerm] dependency, your component will rerender but before it rerenders, the return cleanup function will be called so you will remove the event handler for keydown. After the cleanup is finished, new useEffect will run, console.log({ outside: queryTerm }) will be executed and you will see the updated state
outside:"a"
Since you removed the event handler for keydown before second rerender, this event handler will NOT run. But you will be registering a new event handler for keydown.
now you add "l". handleKeyPress will run so you will see the current state on console
inside:"a"
then async setState will run, state will change, before rerender, your clean up will be fired, then your component will rerender and console.log({ outside: queryTerm }); will be fired so you will see the updated the state
outside:"al"

Why is this event listener not removed?

Based on this popular answer https://stackoverflow.com/a/19014495 I have the following code.
I am trying to understand why "remove effect" is never logged.
function useWindowSize(){
const [size, setSize] = useState([0,0]);
useLayoutEffect(() => {
console.log("use effect");
function updateSize(){
setSize([window.innerWidth, window.innerHeight]);
}
window.addEventListener('resize', updateSize);
updateSize();
return () => {
console.log("remove effect");
window.removeEventListener('resize', updateSize);
}
}, []);
return size;
}
This custom hook is used in a function component
function InfiniteScroll () {
const [width, height] = useWindowSize();
// rest of code should be irrelevant
}
Based on the React documentation an empty array as second argument for the built in hooks means that the effect is used and then cleaned up only once. https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects. I was surprised therefore that this is used in this code snippet because the event listener would be removed immediately. However in testing I discovered that whilst "use effect" is logged "remove effect"is not. Why? What other concept am I missing?
Cleanup effect with an empty dependency array runs on component unmount.
It also mentioned in the docs you shared:
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. This isn’t handled as a special case — it follows directly from how the dependencies array always works.
So having conditional rendering will show the log
const [show,toggle] = useReducer(p=>!p,true);
// Will log "remove effect" on show === false
<>
<button onClick={toggle}>toggle</button>
{show && <InfiniteScroll />}
</>
For more info see useEffect use cases.
The return part in the useEffect hook is a clean-up process that is only happening when the targeted component is unmounting, That is what you're missing try to navigate away or destroy this component and you should see the log message.
I already tried the snippet you showed and I think it's working as expected and showing the remove effect log once.
according to the react docs,
If you want to run an effect and clean it up only once (on mount and
unmount), you can pass an empty array ([]) as a second argument. This
tells React that your effect doesn’t depend on any values from props
or state, so it never needs to re-run.
so on mounting this component the listener already added to window once, and then as long as you are still in the same component you have access to the event listener. on unmount the remove effect will be logged as well. you have to unmount the component to see the remove effect log

React: why is that changing the current value of ref from useRef doesn't trigger the useEffect here

I have a question about useRef: if I added ref.current into the dependency list of useEffect, and when I changed the value of ref.current, the callback inside of useEffect won't get triggered.
for example:
export default function App() {
const myRef = useRef(1);
useEffect(() => {
console.log("myRef current changed"); // this only gets triggered when the component mounts
}, [myRef.current]);
return (
<div className="App">
<button
onClick={() => {
myRef.current = myRef.current + 1;
console.log("myRef.current", myRef.current);
}}
>
change ref
</button>
</div>
);
}
Shouldn't it be when useRef.current changes, the stuff in useEffect gets run?
Also I know I can use useState here. This is not what I am asking. And also I know that ref stay referentially the same during re-renders so it doesn't change. But I am not doing something like
const myRef = useRef(1);
useEffect(() => {
//...
}, [myRef]);
I am putting the current value in the dep list so that should be changing.
I know I am a little late, but since you don't seem to have accepted any of the other answers I'd figure I'd give it a shot too, maybe this is the one that helps you.
Shouldn't it be when useRef.current changes, the stuff in useEffect gets run?
Short answer, no.
The only things that cause a re-render in React are the following:
A state change within the component (via the useState or useReducer hooks)
A prop change
A parent render (due to 1. 2. or 3.) if the component is not memoized or otherwise referentially the same (see this question and answer for more info on this rabbit hole)
Let's see what happens in the code example you shared:
export default function App() {
const myRef = useRef(1);
useEffect(() => {
console.log("myRef current changed"); // this only gets triggered when the component mounts
}, [myRef.current]);
return (
<div className="App">
<button
onClick={() => {
myRef.current = myRef.current + 1;
console.log("myRef.current", myRef.current);
}}
>
change ref
</button>
</div>
);
}
Initial render
myRef gets set to {current: 1}
The effect callback function gets registered
React elements get rendered
React flushes to the DOM (this is the part where you see the result on the screen)
The effect callback function gets executed, "myRef current changed" gets printed in the console
And that's it. None of the above 3 conditions is satisfied, so no more rerenders.
But what happens when you click the button? You run an effect. This effect changes the current value of the ref object, but does not trigger a change that would cause a rerender (any of either 1. 2. or 3.). You can think of refs as part of an "effect". They do not abide by the lifecycle of React components and they do not affect it either.
If the component was to rerender now (say, due to its parent rerendering), the following would happen:
Normal render
myRef gets set to {current: 1} - Set up of refs only happens on initial render, so the line const myRef = useRef(1); has no further effect.
The effect callback function gets registered
React elements get rendered
React flushes to the DOM if necessary
The previous effect's cleanup function gets executed (here there is none)
The effect callback function gets executed, "myRef current changed" gets printed in the console. If you had a console.log(myRef.current) inside the effect callback, you would now see that the printed value would be 2 (or however many times you have pressed the button between the initial render and this render)
All in all, the only way to trigger a re-render due to a ref change (with the ref being either a value or even a ref to a DOM element) is to use a ref callback (as suggested in this answer) and inside that callback store the ref value to a state provided by useState.
https://reactjs.org/docs/hooks-reference.html#useref
Keep in mind that useRef doesn’t notify you when its content changes. Mutating the .current property doesn’t cause a re-render. If you want to run some code when React attaches or detaches a ref to a DOM node, you may want to use a callback ref instead.
use useCallBack instead, here is the explanation from React docs:
We didn’t choose useRef in this example because an object ref doesn’t
notify us about changes to the current ref value. Using a callback ref
ensures that even if a child component displays the measured node
later (e.g. in response to a click), we still get notified about it in
the parent component and can update the measurements.
Note that we pass [] as a dependency array to useCallback. This
ensures that our ref callback doesn’t change between the re-renders,
and so React won’t call it unnecessarily.
function MeasureExample() {
const [height, setHeight] = useState(0);
const measuredRef = useCallback(node => {
if (node !== null) {
setHeight(node.getBoundingClientRect().height);
}
}, []);
return (
<>
<h1 ref={measuredRef}>Hello, world</h1>
<h2>The above header is {Math.round(height)}px tall</h2>
</>
);
}
Ok so I think what you're missing here is that changing a ref's value doesn't cause a re-render. So if it doesn't cause re-renders, then the function doesn't get run again. Which means useEffect isn't run again. Which means it never gets a chance to compare the values. If you trigger a re-render with a state change you will see that the effect will now get run. So try something like this:
export default function App() {
const [x, setX] = useState();
const myRef = useRef(1);
useEffect(() => {
console.log("myRef current changed"); // this only gets triggered when the component mounts
}, [myRef.current]);
return (
<button
onClick={() => {
myRef.current = myRef.current + 1;
// Update state too, to trigger a re-render
setX(Math.random());
console.log("myRef.current", myRef.current);
}}
>
change ref
</button>
);
}
Now you can see it will trigger the effect.

React JS | using setTimeout prints a number on Page

I am simply trying to change the heading and content states using React Hooks but I get a number shown on the page, a little google search showed up a bunch of stuff related to how setInterval and Timeout generate a key or value but I have no idea why they're showing up on the page.I can hide it using an empty div but I am curious if I am doing anything wrong, also if I use a class instead of a function the value rapidly increases and my CPU maxes out.
function MyComponent (){
const [heading, setHeading] = useState('React(Loading)')
const [content, setContent] = useState('Loading...')
return(
<main>
<h1>{heading}</h1>
<p>{content}</p>
{
setTimeout(() =>{
setHeading('React(Loaded)')
setContent('Loaded')
}, 2000)} // should change the values, which it does with addition of a number
</main>
);
}
The resulting page is that renders is here
Also on a side note I tried using a onload function to do the same thing but nothing happens.
setTimeout returns a number, which is used to identify the timeout when you use clearTimeout. That is why you see the number below the content.
To hide the number, you should move the setTimeout to be outside of the return function. Also, you should use as little JS as possible in the return statement and just use JSX over there, to make the component more clear and readable.
But just moving the setTimeout to be before the return statement is not enough. The function will run on every render, and there are many things that can trigger a re-render - a state change, or a parent re-rendering. So on every re-render, you will set a new timeout. The timeout itself updates a state which triggers a render which triggers the setTimeout - so you are creating an infinite loop.
So you want to call setTimeout only once - you can use useEffect, which will re-run only when the dependency array changes, but if you will leave it empty, it will run only once, because nothing will change and a re-run will never be triggered.
function MyComponent (){
const [heading, setHeading] = useState('React(Loading)')
const [content, setContent] = useState('Loading...')
useEffect((
setTimeout(() =>{
setHeading('React(Loaded)')
setContent('Loaded')
}, 2000)
), []);
return(
<main>
<h1>{heading}</h1>
<p>{content}</p>
</main>
);
}
So, by using the above answer we get the following error
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function
The reason for this is again the fact that setTimeout returns a number, the final answer is to use the code as a separate function as below:
useEffect( timeOutFunction, [])
function timeOutFunction() {
setTimeout(() => {
setHeading('React(Loaded)')
setContent('Loaded'), 2000)
}

Can't perform a React State update on unMounted child component?

Am getting this warning:
Can't perform a React state update on unmounted component. This is a no-op...
It results from a child component and I can't figure out how to make it go away.
Please note that I have read many other posts about why this happens, and understand the basic issue. However, most solutions suggest cancelling subscriptions in a componentWillUnmount style function (I'm using react hooks)
I don't know if this points to some larger fundamental misunderstanding I have of React,but here is essentially what i have:
import React, { useEffect, useRef } from 'react';
import Picker from 'emoji-picker-react';
const MyTextarea = (props) => {
const onClick = (event, emojiObject) => {
//do stuff...
}
const isMountedRef = useRef(true);
useEffect(() => {
isMountedRef.current = true;
});
useEffect(() => {
return () => {
console.log('will unmount');
isMountedRef.current = false;
}
});
return (
<div>
<textarea></textarea>
{ isMountedRef.current ? (
<Picker onEmojiClick={onClick}/>
):null
}
</div>
);
};
export default MyTextarea;
(tl;dr) Please note:
MyTextarea component has a parent component which is only rendered on a certain route.
Theres also a Menu component that once clicked, changes the route and depending on the situation will either show MyTextarea's parent component or show another component.
This warning happens once I click the Menu to switch off MyTextarea's parent component.
More Context
Other answers on StackOverflow suggest making changes to prevent state updates when a component isn't mounted. In my situation, I cannot do that because I didn't design the Picker component (rendered by MyTextarea). The Warning originates from this <Picker onEmojiClick={onClick}> line but I wouldn't want to modify this off-the-shelf component.
That's explains my attempt to either render the component or not based on the isMountedRef. However this doesn't work either. What happens is the component is either rendered if i set useRef(true), or it's never rendered at all if i set useRef(null) as many have suggested.
I'm not exactly sure what your problem actually is (is it that you can't get rid of the warning or that the <Picker> is either always rendering or never is), but I'll try to address all the problems I see.
Firstly, you shouldn't need to conditionally render the <Picker> depending on whether MyTextArea is mounted or not. Since components only render after mounting, the <Picker> will never render if the component it's in hasn't mounted.
That being said, if you still want to keep track of when the component is mounted, I'd suggest not using hooks, and using componentDidMount and componentWillUnmount with setState() instead. Not only will this make it easier to understand your component's lifecycle, but there are also some problems with the way you're using hooks.
Right now, your useRef(true) will set isMountedRef.current to true when the component is initialized, so it will be true even before its mounted. useRef() is not the same as componentDidMount().
Using 'useEffect()' to switch isMountedRef.current to true when the component is mounted won't work either. While it will fire when the component is mounted, useEffect() is for side effects, not state updates, so it doesn't trigger a re-render, which is why the component never renders when you set useRef(null).
Also, your useEffect() hook will fire every time your component updates, not just when it mounts, and your clean up function (the function being returned) will also fire on every update, not just when it unmounts. So on every update, isMountedRef.current will switch from true to false and back to true. However, none of this matters because the component won't re-render anyways (as explained above).
If you really do need to use useEffect() then you should combine it into one function and use it to update state so that it triggers a re-render:
const [isMounted, setIsMounted] = useState(false); // Create state variables
useEffect(() => {
setIsMounted(true); // The effect and clean up are in one function
return () => {
console.log('will unmount');
setIsMounted(false);
}
}, [] // This prevents firing on every update, w/o it you'll get an infinite loop
);
Lastly, from the code you shared, your component couldn't be causing the warning because there are no state updates anywhere in your code. You should check the picker's repo for issues.
Edit: Seems the warning is caused by your Picker package and there's already an issue for it https://github.com/ealush/emoji-picker-react/issues/142

Resources