Cool idea! But my honest opinion is, if you find yourself looking for conditionally rendering hooks, you might have issues in your components composition. Hooks are meant to enrich components with shareable logic. So if you want to conditionally use it, it indicates that you should just split up your components like mentioned in some other comments. Then you only apply the hook to the component that actually needs it.That would be the "clean" way to conditionally render hooks. Make's the API also much cleaner and easier to follow along. Adding these "enabled" arguments makes it very hard to reason about.
Well, not always true. Imagine a fetch hook that you want to run based on some condition which is something very common. In this case the pattern shown in video comes handy. In fact, this pattern is used by React Query, you can pass a enabled argument to skip/run the hook between rerenders.
@@mortsdans i agree with u. In most cases when u only need to run logics. Create a child comp instead of hook. return the empty react fragment. write the logic in that comp and conditionally render that Child comp. I believe it will be more feasible in this use case.
hooks are easier to use in various situations than parent/child components. you can use the exact same hook in a drawer component with ease, for example
you could, but this Dialog is the main component, breaking it up into a child is an extra layer and it's more code/work than simply adding the isEnabled flag. But yes you could do both, this is not an absolute rule and is more useful for understanding how to handle conditional hooks, when you want/need to use them
yeah, bad example for this "pattern". and some pattern it is, who needs 13 minutes to explain that if you want to skip some code, you need to place early return statement in that code and not in a code which is called from code you want to skip? It is just common sense applied on top of basic understanding of how hooks work. Proper pattern would be to refactor the dialog to introduce another component which is mounted conditionally behind isOpen and put the hook call inside that component, problem solved, hooks can't be put in conditional, but components can.
Love this pattern. Gained knowledge about it from using Tanstack Query and experimenting with building my own query hook and attaching the enabled flag. Awesome video, per usual!
12:55 explanation about complexity of parameters after cb, but why not just simply pass onClose and not the arrow function? P.s. for those who don't get it - with parentheses
In this example better solution is to use composition. Create Dialog and Dialog View components. Inside Dialog View use useOutsideClick hook (and encapsulated all logic in here). Inside Dialog return isOpen ? : null.
this is an unecessary abstraction. your solution adds an extra component with no real benefit, more layers of logic just makes things harder to reason about.
@PraiseYeezus benefit is simple DialogView only render content itself (SRP - single responsibility principle), Dialog render DialogView (or AnyComponent) and mount them in DOM. )))
That way of programming trying to respect every single "principle" you've ever heard of makes you a bad programmer. The solution shown in the video is the objective best way to do it. The hook itself is responsible for its whole life cycle, no need for an extra component to handle the condionnality just to respect some principle. Your "solution" implies to create a "handler component" everywhere you use the hook. You want to use it on , oh now you need a . On , now you need a . This is just dumb and doesn't make the code clearer or more readable. The only benefit is making you feel good for respecting some principle that should be taken with a grain of salt, not applied universally. The thing is that you already have a variable for the state of the Dialog, DropDown or whatever (opened or closed). Would it be so bad to pass that state to the hook and handle the logic in there as shown in the video?
Hey What about arrow function in hook `useOutsideClick` in `Dialog` component? Not memoized => a new function is created with every re-render => `useEffect` run's every re-render..
Very nice, I too would prefer it at the start of the args, I tend to put my callbacks at the end due to the formatting, though I do tend to pass in a function as a call back so it's just cb={myfunction}
Oh! I have two concerns. Please forgive me for my bluntness. Concern #1: Example We have violated the principle of separation of concerns. A single component is responsible both for deciding whether to render and for rendering itself. It seems much simpler to extract isOpen outside of the component and manage it at a higher level, like this: {isOpen && }, rather than updating a hook that is often sourced from external packages (so we need to create another one that wraps original). Concern #2: The video title doesn't match its content. We still follow the same approach: a fast conditional return from the effect-callback. However, we now need to pass an additional parameter. Have a nice coding to all )
So the pattern is to pass the conditional to the custom Hook and call the custom hook as we normally do. Nice to see you use Vim modes inside VSCode. I recently switched into Neovim and Im loving it. Do you use Vim together with VSCode in your daily job or just is for teaching purposes?
I always waste my time wondering how to make my code cleaner, wondering about very little things, eg. in this case i would wonder, should i use enabled flag or instead use just callback as a flag, if there is a callback it's equal to enabled, if !open then i'd pass undefined instead of callback.
This falls down when you have hooks that contain other hooks. I have faced this problem also and for some expensive hooks used a wrapper component, wasnt to pretty and I do wish there was a built in way to have some conditions to hooks. Particularly with using things like react query. It’s not so much of a problem on small amounts of components but with large datasets you can really notice even setup time for some hooks gives a performance hit.
Am I correct in thinking that once the dialog is opened once, the click event is now registered and your back at square one with the click event in memory even when the dialog is closed? This only helps with performance on first render/mount, right? Would it be beneficial to remove the event listener when enabled becomes false?
The return statement in the useEffect cleans up the event listener when the hook unmounts, which happens right before the hook reruns when enabled (i.e. isOpen) becomes false.
Apparnetly if you're using a useEffect you've probably messed up your architecture? That's the consenus I've got from industry-working seniors. Redux or Tanstack Query generally replaces useEffect, depending on the situation.
not at all. I use useEffect all of the time. Sure there's an argument for not using useEffect, and I always question if I really need it, but there are many use cases still where it's the only option, like here with event listeners
@@theReal_WKD People giving talks at conferences for example, blog posts, RUclips videos. There's a lot of controversary around it as it may be handled more gracefully with state management. "Cons of useEffect Over-reliance for Derived State Logic Using useEffect to compute derived state can lead to unnecessary re-renders and complex code. Derived state should instead be computed directly inside the render logic or using useMemo. Risk of Dependency Management Issues Forgetting to include dependencies or adding the wrong dependencies can cause bugs, infinite loops, or stale data. Managing dependencies correctly can be tricky, especially in complex components. Performance Concerns If not used carefully, useEffect can introduce performance bottlenecks by triggering unnecessary re-executions or causing heavy operations during re-renders. Harder Debugging Side effects are harder to test and debug compared to pure functional logic. Issues like race conditions, stale closures, or unintended dependencies can make debugging challenging. Overhead for Cleanup For effects requiring cleanup (e.g., subscriptions, event listeners), you must implement a cleanup function, adding complexity to the code. Concurrency Issues with React 18 In React 18's concurrent rendering, useEffect may be triggered multiple times due to its nature, which can cause unexpected behavior if effects are not idempotent or properly handled."
I think my solution works well, or even better, because the hook is used only when the component needs it. What do u think about it? ``` export default function Dialog({ isOpen, onClose, }: { isOpen: boolean; onClose: () => void; }) { return isOpen ? : null; } function DialogContent({ onClose}: { onClose: () => void;}) { const elementRef = useRef(null); useOutsideClick(isOpen, elementRef, () => { onClose(); }); return } ```
@@_a_9773 There will be no listeners if `isOpen` is false, because the `DialogContent` function will not be called. The `DialogContent` component will only mount/unmount if the value of `isOpen` changes.
IK I mean everytime u toggling the isOpen variable the Component is mounted/unmounted which means the event lkstners will be added/removed as well as new DOM elements. A less expensive solution is to hide the dialog
@@_a_9773 The video shows a simple example. Imagine that you have several different dialogs with more complex functionalities than a dialog that only has a single close function.
@@_a_9773 It's actually more expensive to keep the DOM node with its listeners all the time. A user might never actually use that dialog. That's why you would actually want to use things only when they are actually needed. The same strategy applies to simple `on{Event}` props. Imagine rendering a component which children has an `onClick` for example. Parent gets unmounted, its children listeners gets cleaned up, then DOM nodes gets removed.
If isOpen is already state and changing its value rerenders that dialogue and wherever else it is being used, you dont even need a use effect right? Since isOpen itself is reactive, you can just use vanilla js to conditionally add the click event.
You can remove event listeners with just JavaScript as well though. You can also use the once option on the event listener which once invoked will be removed. Many ways to clean up without having to use useEffect. I agree it might go away from idiomatic react however.
Why couldn't `if(!isOpen) return null` be called before useOutsideClick or before useRef? There would be no need to create a useEffect or another useRef
well, solution is ok, but you can do better in my opinion, so for fixing react problen just use react, create component DialogOpend with this hook inside and render it conditionally inside Dialog, here we go, issue fixed and no code should rely on some pattern. Also I want to hilight a proplem, since you passing callbac function in dependency array useEffect will run every time callback is changed, but it changes on each render sinse you do did not use useCallback hook on it, that means adding and removing listener will be performed on each renderr so it will impact performance.
" you typically don’t need to pass isOpen to the useOnClickOutside hook. Instead, the hook should be responsible only for detecting an outside click and triggering a callback (onClose). This keeps the hook focused and reusable without concern for whether the component is open or closed. However, you do pass the elementRef (the reference to the DOM element you want to monitor for outside clicks) and a callback (like onClose) that runs when an outside click is detected. The hook doesn’t need to know or control the open/closed state-it just triggers the callback, and the component manages the state accordingly."
"State Management: The Dropdown component manages isOpen, keeping state logic where it belongs and allowing other components to use the hook with their own logic."
Just create a child component and encapsulate the logic. I don't recommend the pattern shown with "enabled". For me it is less readable and maintainable.
Good tutorial, but it only took me a few minutes to solve it. using stackoverflow and chatgpt is still faster than playing your video in x2 play speed. just saying
Not bad pattern. But OMG, video is too long for such feature. 2 mins max.
RUclips monetistion rate is increased for videos over 10 mins
I disagree
@@mercyiskey9009 🤡🤡🤡
i agree, took until after the add (5min) to start getting to the point
It better to create a child component instead of modifying the hook
Yep
Thought about the same
I didn't get it
can you elaborate please? create a child component and then what?
@@babub9617 can you elaborate please? create a child component and then what?
Cool idea! But my honest opinion is, if you find yourself looking for conditionally rendering hooks, you might have issues in your components composition. Hooks are meant to enrich components with shareable logic. So if you want to conditionally use it, it indicates that you should just split up your components like mentioned in some other comments. Then you only apply the hook to the component that actually needs it.That would be the "clean" way to conditionally render hooks. Make's the API also much cleaner and easier to follow along. Adding these "enabled" arguments makes it very hard to reason about.
Well, not always true. Imagine a fetch hook that you want to run based on some condition which is something very common. In this case the pattern shown in video comes handy. In fact, this pattern is used by React Query, you can pass a enabled argument to skip/run the hook between rerenders.
Can't you just create a child component that calls your hook, and conditionally render the child component if open?
That wouldn't suffice all use cases. Some hooks are just plain logic, meaning no JSX involved.
@AtizaJuanita right but the hook logic doesn't even run unless the component the hook is in renders
@@mortsdans i agree with u. In most cases when u only need to run logics. Create a child comp instead of hook. return the empty react fragment. write the logic in that comp and conditionally render that Child comp. I believe it will be more feasible in this use case.
hooks are easier to use in various situations than parent/child components. you can use the exact same hook in a drawer component with ease, for example
you could, but this Dialog is the main component, breaking it up into a child is an extra layer and it's more code/work than simply adding the isEnabled flag. But yes you could do both, this is not an absolute rule and is more useful for understanding how to handle conditional hooks, when you want/need to use them
if the dialog isn't open then the ref.current is undefined and you already have a return statement that checks for that.
The answer should probably be on top as a better solution for this hook
THIS. This video is just terrible advice.
yeah, bad example for this "pattern". and some pattern it is, who needs 13 minutes to explain that if you want to skip some code, you need to place early return statement in that code and not in a code which is called from code you want to skip? It is just common sense applied on top of basic understanding of how hooks work.
Proper pattern would be to refactor the dialog to introduce another component which is mounted conditionally behind isOpen and put the hook call inside that component, problem solved, hooks can't be put in conditional, but components can.
Simple and best solution, thank you so much for making a video on this
It would have been nice to mention the source article u used for the video since u using same example and mostly same explanation
what is the article? how to find it?
Just google "Condition react hook Robin Malfait"
@TimaGixe Conditional react hooks Robin Malfait
@@_a_9773 Thanks for sharing.. Im following Robin now.
@@_a_9773 thanks!
9:40 That function handle on line 15 will still be defined and temporarily stored before the interpreter executes the return statement.
Love this pattern. Gained knowledge about it from using Tanstack Query and experimenting with building my own query hook and attaching the enabled flag. Awesome video, per usual!
12:55 explanation about complexity of parameters after cb, but why not just simply pass onClose and not the arrow function? P.s. for those who don't get it - with parentheses
In this example better solution is to use composition. Create Dialog and Dialog View components. Inside Dialog View use useOutsideClick hook (and encapsulated all logic in here). Inside Dialog return isOpen ? : null.
this is an unecessary abstraction. your solution adds an extra component with no real benefit, more layers of logic just makes things harder to reason about.
@PraiseYeezus benefit is simple DialogView only render content itself (SRP - single responsibility principle), Dialog render DialogView (or AnyComponent) and mount them in DOM. )))
That way of programming trying to respect every single "principle" you've ever heard of makes you a bad programmer. The solution shown in the video is the objective best way to do it. The hook itself is responsible for its whole life cycle, no need for an extra component to handle the condionnality just to respect some principle. Your "solution" implies to create a "handler component" everywhere you use the hook.
You want to use it on , oh now you need a . On , now you need a . This is just dumb and doesn't make the code clearer or more readable. The only benefit is making you feel good for respecting some principle that should be taken with a grain of salt, not applied universally.
The thing is that you already have a variable for the state of the Dialog, DropDown or whatever (opened or closed). Would it be so bad to pass that state to the hook and handle the logic in there as shown in the video?
Nice. This same pattern is used by Tanstack Query to skip/run between rerenders. I've also used it in my projects.
That's so interesting, thank you very much!
I literally use this same pattern in one of our hooks to conditionally fetch api data. Default is true.
RTK Query has a similar pattern with the skip object/skipToken
@ryanneil4020 we don't use RTK Query in our project so had to write one myself.
I like and subscribe, excellent content, I will watch the videos in order, keep it up!
Won't the main use effect be triggered on each render since you pass a function without calling use call back?
Hey
What about arrow function in hook `useOutsideClick` in `Dialog` component?
Not memoized => a new function is created with every re-render => `useEffect` run's every re-render..
always useful tips and great content this channel is a gem
Very nice, I too would prefer it at the start of the args, I tend to put my callbacks at the end due to the formatting, though I do tend to pass in a function as a call back so it's just cb={myfunction}
Oh! I have two concerns. Please forgive me for my bluntness.
Concern #1: Example
We have violated the principle of separation of concerns. A single component is responsible both for deciding whether to render and for rendering itself. It seems much simpler to extract isOpen outside of the component and manage it at a higher level, like this: {isOpen && }, rather than updating a hook that is often sourced from external packages (so we need to create another one that wraps original).
Concern #2: The video title doesn't match its content.
We still follow the same approach: a fast conditional return from the effect-callback. However, we now need to pass an additional parameter.
Have a nice coding to all )
Very good 👍🏼
So the pattern is to pass the conditional to the custom Hook and call the custom hook as we normally do.
Nice to see you use Vim modes inside VSCode. I recently switched into Neovim and Im loving it. Do you use Vim together with VSCode in your daily job or just is for teaching purposes?
I always waste my time wondering how to make my code cleaner, wondering about very little things, eg. in this case i would wonder, should i use enabled flag or instead use just callback as a flag, if there is a callback it's equal to enabled, if !open then i'd pass undefined instead of callback.
This falls down when you have hooks that contain other hooks.
I have faced this problem also and for some expensive hooks used a wrapper component, wasnt to pretty and I do wish there was a built in way to have some conditions to hooks.
Particularly with using things like react query.
It’s not so much of a problem on small amounts of components but with large datasets you can really notice even setup time for some hooks gives a performance hit.
Am I correct in thinking that once the dialog is opened once, the click event is now registered and your back at square one with the click event in memory even when the dialog is closed? This only helps with performance on first render/mount, right? Would it be beneficial to remove the event listener when enabled becomes false?
The return statement in the useEffect cleans up the event listener when the hook unmounts, which happens right before the hook reruns when enabled (i.e. isOpen) becomes false.
@dcmbassi oh ok, didn't realize the hook would essentially unmount when rerun but that makes sense. Thanks for the explanation 👌
your videos make me question my react fundamentals lol
Apparnetly if you're using a useEffect you've probably messed up your architecture? That's the consenus I've got from industry-working seniors.
Redux or Tanstack Query generally replaces useEffect, depending on the situation.
not at all. I use useEffect all of the time. Sure there's an argument for not using useEffect, and I always question if I really need it, but there are many use cases still where it's the only option, like here with event listeners
@@cosdensolutions Thanks for clarifying!
Who are these seniors? People are so afraid of useEffect and it just sounds like a skill issue at some point...
@@theReal_WKD People giving talks at conferences for example, blog posts, RUclips videos. There's a lot of controversary around it as it may be handled more gracefully with state management.
"Cons of useEffect
Over-reliance for Derived State Logic
Using useEffect to compute derived state can lead to unnecessary re-renders and complex code. Derived state should instead be computed directly inside the render logic or using useMemo.
Risk of Dependency Management Issues
Forgetting to include dependencies or adding the wrong dependencies can cause bugs, infinite loops, or stale data. Managing dependencies correctly can be tricky, especially in complex components.
Performance Concerns
If not used carefully, useEffect can introduce performance bottlenecks by triggering unnecessary re-executions or causing heavy operations during re-renders.
Harder Debugging
Side effects are harder to test and debug compared to pure functional logic. Issues like race conditions, stale closures, or unintended dependencies can make debugging challenging.
Overhead for Cleanup
For effects requiring cleanup (e.g., subscriptions, event listeners), you must implement a cleanup function, adding complexity to the code.
Concurrency Issues with React 18
In React 18's concurrent rendering, useEffect may be triggered multiple times due to its nature, which can cause unexpected behavior if effects are not idempotent or properly handled."
I think my solution works well, or even better, because the hook is used only when the component needs it. What do u think about it?
```
export default function Dialog({
isOpen,
onClose,
}: {
isOpen: boolean;
onClose: () => void;
}) {
return isOpen ? : null;
}
function DialogContent({ onClose}: { onClose: () => void;}) {
const elementRef = useRef(null);
useOutsideClick(isOpen, elementRef, () => {
onClose();
});
return
}
```
Ur solutiom is mounting and unmounting the component everytime which gonna setup event listners and allocate memory over and over
@@_a_9773 There will be no listeners if `isOpen` is false, because the `DialogContent` function will not be called. The `DialogContent` component will only mount/unmount if the value of `isOpen` changes.
IK I mean everytime u toggling the isOpen variable the Component is mounted/unmounted which means the event lkstners will be added/removed as well as new DOM elements. A less expensive solution is to hide the dialog
@@_a_9773 The video shows a simple example. Imagine that you have several different dialogs with more complex functionalities than a dialog that only has a single close function.
@@_a_9773 It's actually more expensive to keep the DOM node with its listeners all the time. A user might never actually use that dialog. That's why you would actually want to use things only when they are actually needed. The same strategy applies to simple `on{Event}` props. Imagine rendering a component which children has an `onClick` for example. Parent gets unmounted, its children listeners gets cleaned up, then DOM nodes gets removed.
Nice!
Can you make a video for creating product tour in react application ?
If isOpen is already state and changing its value rerenders that dialogue and wherever else it is being used, you dont even need a use effect right? Since isOpen itself is reactive, you can just use vanilla js to conditionally add the click event.
you need useEffect to remove the listener when isOpen changes, otherwise you have a memory leak
You can remove event listeners with just JavaScript as well though. You can also use the once option on the event listener which once invoked will be removed. Many ways to clean up without having to use useEffect. I agree it might go away from idiomatic react however.
Why couldn't `if(!isOpen) return null` be called before useOutsideClick or before useRef?
There would be no need to create a useEffect or another useRef
Extremely clever solution!
Or also you can put conditional hooks in new conditionaly rendered component
well, solution is ok, but you can do better in my opinion, so for fixing react problen just use react, create component DialogOpend with this hook inside and render it conditionally inside Dialog, here we go, issue fixed and no code should rely on some pattern.
Also I want to hilight a proplem, since you passing callbac function in dependency array useEffect will run every time callback is changed, but it changes on each render sinse you do did not use useCallback hook on it, that means adding and removing listener will be performed on each renderr so it will impact performance.
I have no idea if there ever is a use case for this but what about default react hooks?
What do you mean if there is a use case. He jusg explained the use case
@_a_9773 i said default hooks. Like useState and useEffect
I don't get why all ur videos is always this long, simple concept shouldn't take this long to explain
The best way around this is to leave state in the server and just you JavaScript
elementRef in the useEffect deps 💀
I dont need the course, im a senior dev but your videos are so fucking good that im gonna buy it later just to support you
imo this is the wrong pattern. just move is open outside. this will be most performant code and it will be cleaner as well.
" you typically don’t need to pass isOpen to the useOnClickOutside hook. Instead, the hook should be responsible only for detecting an outside click and triggering a callback (onClose). This keeps the hook focused and reusable without concern for whether the component is open or closed.
However, you do pass the elementRef (the reference to the DOM element you want to monitor for outside clicks) and a callback (like onClose) that runs when an outside click is detected. The hook doesn’t need to know or control the open/closed state-it just triggers the callback, and the component manages the state accordingly."
"State Management: The Dropdown component manages isOpen, keeping state logic where it belongs and allowing other components to use the hook with their own logic."
Just create a child component and encapsulate the logic. I don't recommend the pattern shown with "enabled". For me it is less readable and maintainable.
Did bro just make us edge in react? 😶😶
For an enabled flag?
Good tutorial, but it only took me a few minutes to solve it. using stackoverflow and chatgpt is still faster than playing your video in x2 play speed. just saying