In part 1 they talk about everything being a function including react components and closures. But I come from the "closure is a poor mans object and a function is a poor mans closure" perspective. In other words everything is an object. This is most likely because I started programming in the early 90s when first-class functions were not that common in popular languages (C had function pointers but it wasn't pretty)
But either way it works fine. Everything is a first class 'thing' that has properties that can be parameterised. If the 'thing' is an object the properties are alive and can be mutated (if mutable) for the lifetime of the object. If the 'thing' is a function the properties (arguments and local vars) are alive for the duration of the call.
When first class functions became mainstream though, I couldn't really understand the idea that they were fundamentally new compared to objects (in fairness this was only put forth by a few people)
I lean towards closures having primacy. FTR, I learned OOP way before I'd ever heard of functional programming.
My reasoning is based on two things: Firstly, objects have identity and this is a superfluous notion for most cases. Functions just "do things" to their inputs and return the result. Secondly, OOP is fundamentally predicated on the notion of mutation[0], but I think that is totally antithetical to local reasoning (chunking). The latter may just be a preference, but IME it seems to bear out. Mutation seems to be a consequence of the focus on identity, now that I think about it after writing that footnote.
> I couldn't really understand the idea that they were fundamentally new compared to objects (in fairness this was only put forth by a few people)
Just a historical note: First-class functions were invented waaaaay before OOP was a gleam in somebody's eye.
[0] The only non-mutating OOP system I know of is O'Camls version of OOP, but it's not very popular because it's just... unnecessary. It also does away with "identity", now that I think about it.
I would say the biggest JavaScript inflicted pain when working with React is the lack of a notion of value equality in the default mutable collections.
Coming from a ClojureScript background, it pains me to have to deal with all the idioms in React that hack around this fundamental impedance mismatch by forcing users to carefully maintain reference equality for collections between renders for perf optimizations, and more recently for correctness in hooks like useEffect.
JS... the "winner" of the Accidental Complexity game. (It was made in a couple of weeks, so it's excusable, but... it just hurts so much that this was the language that conquered the web :| )
Yes, and realizing there is probably a larhe subset of people who'd say they love JS but hate PHP can make a man who know both too well confused. Or at least aware of the hypocrisy ;-)
To be fair, PHP5 really was quite a nightmare for honestly a lot of reasons that JS was never. I think most people remember PHP5 and compare it to fairly modern JS, but I must say, modern PHP is a lot better than older PHP (i still prefer just about anything else.)
Yes, to be clear, I agree. I think most people just don’t realize PHP actually did improve. I’ll admit until digging into a modern PHP library I didn’t even believe it could be meaningfully better.
Thank you for reading both articles! (author here).
When I write these sometimes-overly-simplified-articles I'm terrified of explaining something wrong, or that my knowledge may be incorrect on a very specific point.
I really like the way you express "everything is a first class 'thing'", I never thought of it that way.
FWIW I thought they were both pretty spot on. In fact, you mentioned this about DNS:
"As I read articles and tried stuff out (and broke my server config more than once) I started to grasp the system, to get glimpses into how it all worked, until eventually it “clicked” and I felt comfortable working with it."
Have you written about your mental models for DNS? Color me interested!
"closure is a poor mans object and an object is a poor mans closure" is true in principle. Devil's in the details. If the language forces the declaration of the object equivalent of a closure in a different scope, or even a different file, then massaging captured variables by hand, it makes objects a whole lot more laborious to use, especially for those using rapid iteration to get a feel of the design space.
I can see why objects and closures are reducible to each other. As a Java dev who's written listener objects, closures seem like simple sugar to me. They hold private state or references to state. Functions can be passed references to mutable data so they are equivalent as well.
However, there is the concept of a pure function (one that is unaffected by and causes no side effects) which I think can be argued is fundamentally different.
I think a correction needs to be made in Part 2 "How Does State Change"[0]. The author says "Long story short, don’t expect state to be updated right away" in the context of using `searchValue` right after calling `setSearchValue`. This gives the reader the impression that state is sometimes updated right away and sometimes not so we can't depend on it. However, it's impossible for a useState variable (e.g. `searchValue`) to be updated without a re-render [1]. Thus, using `searchValue` immediately after `setSearchValue` will always be outdated. So state is only updated after render.
[1] The `setSearchValue` updates a value that's outside (and captured) by the closure, React will re-render the component since setSearchValue was called (idk how this works behind the scenes and i think rendering is async), during the re-render `searchValue` and `setSearchValue` are assigned values again, and thus `searchValue` will finally be the updated value.
I haven't used React since before hooks were introduced but I switched to Svelte a couple of months ago and now I find the cognitive load of using React totally unnecessary. Even more now with hooks.
In Svelte you never think about these or other considerations (eg: useMemo). Just update some state and that's it. Svelte will render consistently faster than React, with less code to write (and maintain).
It's true with React you get a bit more flexibility but I have yet to find a situation that cannot be solved with Svelte.
To say that you never have to think about those considerations in Svelte is pretty unfair - reactive declarations/statements are functionally equivalent to hooks, and they also carry a somewhat nonintuitive cognitive load until you've begun to "think in reactivity". It's also worth considering that hooks, while unintuitive in function, do use intuitive, non-magical syntax (unlike Svelte's reactive syntax). FWIW I like both, I just think they're both equally unintuitive until you make the mental connection.
I guess we're all different, but I've never had to think much about reactivity in Svelte.
Most of it can be explained in a HN comment:
Component state just does its thing. You change it with = and the component is re-rendered.
Writable stores need to trigger changes with set() and you need to use $ whenever you reference it in a .svelte file so that Svelte handles subscription automatically.
Reactive statements $: are a bit more complex. Every variable inside those is referenced by the compiler so that it knows when you are mutating it. Either with = or if it's a writable store with set().
I have to agree. I still haven’t used hooks yet and I’m sure I’ll get used to them when I do, but by comparison to this article Svelte is a breath of fresh air. It’s simple, it works. A lot of people object to it because it’s very different to React, but judging by the diagrams in the mental models here I don’t think that’s any bad thing.
An aside, and I know complaints about hooks are very boring now, but I remember when React first arrived on the scene. The API was a delight. shouldComponentUpdate, willReceiveProps, etc. Everything was so clear from the first second you picked it up. Now? I’m less sure.
I like Svelte a lot, but there’s definitely cognitive load when dealing with reactivity. In React, it’s top-down dataflow, which is generally easy to reason about.
I prefer the functional programming approach of React, but I love the performance and some of the developer ergonomics of Svelte.
You don't have to use two way binding binding in Svelte. You can bind to change events just like in React and update your reactive variables directly. The primary downside is enforcing it in the code everyone else on your team writes.
I learned a lot about useEffect thanks. I initially clicked for the visual explanation but ended up enjoying the written explanation more.
I think the problem with your visuals is the lack of depth in the colors. Especially because of the gradients, all the parts kind of blend together, so there’s no clear distinction between a parent component and its children for example. I think having clear distinct colored boxes with borders would help.
I really like your style of explaining. Your diagrams are ace too. I like how they’re not too polished, they’re focused on conveying your message. You have a real talent for explaining stuff
Thank you so much! That's in my pipeline. I'm going to work first on an in-depth explanation of useEffect since it's the most used one, and I'll cover all the hooks at some point.
The only thing is that a lot of the same ideas are repeating themselves, but the specifics are worth explaining.
While equivalent to a proper approach, it feels like hooks are an unnecessary hack.
What could be done is to have the component function return a closure that returns the VDOM nodes instead of the VDOM nodes themselves, and then the hooks would be run only once, since rerendering would only run the returned closure and not the whole component function, and there would be no need for the magic or rules.
Like this:
const Component = component((comp) => {
const [show, setShow] = useState(comp, false);
return (...props) => (
<div>
<button onClick={() => setShow(!show())}>Show Menu</button>
// Mounted with show = true and unomunted with show = false
{show() && <MenuDropdown />}
</div>
);
});
Where the changes are that the hook takes the component as an argument, show is now a function rather than a value and a closure (that takes the props) is returned rather than returning the VDOM nodes directly.
The React Hooks API is essentially equivalent to this, but inelegant, unintuitive and somewhat inefficient.
What's the benefit of this over class syntax? If we had decorators in the language already we could make field reactive:
class Component extends React.Component {
@state show;
render() {
return
<div>
<button onClick={() => this.show = !this.show}>Show Menu</button>
// Mounted with show = true and unomunted with show = false
{this.show && <MenuDropdown />}
</div>
}
}
Hiding the underlying stateful objects that back React components seems like it causes more confusion and complicated APIs than it's worth. Likewise closures returning closures and state defined with function calls seems like re-inventing classes.
Just make the component itself stateful and the lifecycle explicit and it's all so much simpler. Then attack the problem of composing JS classes from pieces with tools available to all JS classes, not just the framework at hand.
It seems like the main benefit the authors of hooks get at is being able to encapsulate a library's logic into hooks that you can reuse, so eg. you could do `const [data, loading] = useQuery('some query');` and the query is aware of lifetime etc. without needing to use mixins or multiple inheritance or traits or something else like the component or ECS paradigms from game engines (that each have their pros and cons).
You could have a stateful `this.query` object in your class, but you basically have to spread the code for that around (calling mount / unmount handlers or events) vs. just mentioning it in one place and having it be able to use more hooks internally etc.
It's basically like an `import` statement for your component.
I understand that you have to wire up the lifecycle methods this way, but it's at least low-magic and easily understandable. I don't mind it.
And even though decorators aren't standard yet, they can be used for composition too.
My point is that these problems aren't unique to React and deserve patters and solutions for all JavaScript programming. React is statefull and object-based under the hood, but just trying to hide it.
(author here) I don't have anything to add, you guys have great arguments, I just want to show appreciation for starting a good debate based on a code example I did :)
I agree with the grandparent that hooks are a weird hack and it drives me nuts every time I have to create one and can't use a closure to precompute some stuff and have better control of the lifecycle on functions/data outside the actual render function. So in the end of all this I've found I much prefer the class syntax cause it just makes more sense and does a much better job (for me) at declaring code patterns and shape than trying to follow code paths in/out of different effects (which oddly enough reminds me of the horror days of following code paths through multiple levels of inheritance).
However, there is a major value hooks provide over class components and that's the composability of the lifecycle effects. The problem with class components is that code for a lifecycle is broken across multiple functions and interweaved with other effects. This happens because the declarative model of the component tree clashes with the imperative ordering we code up for lifecycle functions attached 'within' each class component. The fix is to turn lifecycle effects into first level declarative concepts themselves (like in hooks) so they're not 'within' the component, instead they're more of a has-a relationship.
This would actually be rather simple to do, drop the lifecycle methods in a component, and add a new Component.addEffect function (or, cause I'm a big fan of decorators, you could have some decorator to declare what effects should be used). And each effect would be another class that inherits from Effect. I think the hardest part to think about is how you interweave the effects like people do in hooks as one output becomes the input for another, but I think this is probably the wrong way to think about it. Instead if you think of it as an Effect tree then each Effect is just a 'render' function where you're passing in other effects as the props and the output is passed down the tree, so you'd just need an inline effect function that would compose 2 effects together the same way we do with components. At this point the conceptual model is extremely easy cause it's declarative trees all the way down!
Anyone wanna pass this idea onto the react team that'd be awesome cause I'd love to see this added for Class components to make them more composable as well as having better sharing of ideas between hooks and class components :)
> What could be done is to have the component function return a closure that returns the VDOM nodes instead of the VDOM nodes themselves, and then the hooks would be run only once, since rerendering would only run the returned closure and not the whole component function, and there would be no need for the magic or rules.
what could be done is actually using a proper reactive language where things look like this, instead of hacking HTML, JS and whatnot in an unholy mess of syntax:
Button {
text: "Show menu"
onClicked: menu.visible = !menu.visible
}
Menu {
id: menu
}
This syntax might look like it require less magic, but you still have to implement everything react does for hooks that takes care of re-rendering cycles.
the counter's value isn't leaking out, you're explicitly returning it. that's the point – the only thing you can "get out of" a closure is what it returns when you call it
(see also: the JS pattern of using closures to create objects with truly private "fields")
i considered adding "mutate a global" in there but didn't feel like that was relevant to @iooi's comment. maybe i should've quantified "the only thing" a bit more :)
> React state is scoped to the outer-most box, that way it doesn't change on every render
The state (of the hooks) for individual components are still scoped to that component only, its just stored in the "outer-most box", and the structure corresponds to the component tree. If a component unmounts and remounts, the prior state is lost.
You're correct, this is something I tried to convey in my article but I didn't want to spend too many words on it.
When retrospecting about it, I realized I didn't think of it this way, even though it's correct. My mental model is like "state is just kept, don't worry about it".
Thank you for pointing it out though, it's hard to be accurate and succinct
Another day, another site that's just text and images but for some reason won't work with cookies disabled. I know not allowing arbitrary cookies on my machine en masse is my own choice, but I cannot comprehend why a page like this wouldn't work without them.
Yes but sometimes I want a choice. For example, I use the Dark Reader extension with a pure #000 background. Many dark modes use some grey or other non-black hue, which can be quite annoying, so I usually turn them to light mode and then Dark Reader can properly convert the light theme into my preferred black theme.
I'm aware of the prefers-color-scheme stuff added to browsers. AFAICT, only Firefox supports setting it at the browser level, while others will inherit from the system.
This would still remove the visitors ability to say "I want THIS SITE to be in light/dark mode", without affecting all other sites / apps on their system. This page could support reading from the browser/OS, while still giving the choice on top of that.
Again, this choice was probably implemented ... to give the user a choice.
May I ask (genuine question), why would anyone need a choice per site? Surely some people are “light mode” people and want everything in that mode and others are “dark mode” people?
As the author of the site, I like switching between the two modes on all websites that support it.
Like someone above said, some sites implement it as an after-thought. In my case, I'm giving more love to dark-more because there are simply more nice color combinations in dark more than with white.
The author here: indeed! I've debated myself over and over whether I should make dark mode the default, but I'll implement the "preferred theme" someone mentioned in another comment :)
It's cool that you fixed this, but you shouldn't apologize to this person. You published a free article on your own site. This commenter rudely criticized your site for their nontypical edge case without an ounce of respect. They are lucky to have gotten your attention at all.
This is my sentiment. I'd feel frustrated as well if I was on their shoes (visited an interesting article, but it didn't work because of something silly).
It was also unexpected. I didn't even know localStorage could be broken by disabling cookies haha.
> though it's obvious they're frustrated (understandably)
It reminds me of the meme where a guy is riding his bike, inserts a stick into his own wheel while riding, falls over (understandably), and then blames someone else for his fall.
When I visit a news site, to read an article, I get a video that scrolls along with me and autoplays. So I disable video autoplay.
This breaks some video conferencing apps. It's a choice I have to make - some broken apps or a bunch of annoying websites.
Some websites use my cookies and localstorage to track me across properties. They do this so they can harvest my profile data and habits and sell them for profit without my permission.
Again, it's a choice and I think it's a fair one to make.
As engineers, we don't exist to punish our users for making choices different from ours. That's a great way to end up without any users. We should make their experience as good as we can and give them value. Sometimes that isn't possible - I can't make a video conference app that's immune to blocking autoplay. That doesn't mean we shouldn't try.
I whole-heartedly agree with this. Unless you implement a must-have feature based on localStorage or whatever, your site should still fully (or at least partially) function without it.
In my case, I was aghast my blog didn't work without cookies. I don't even have ads running on it, and it's static, it should work on my grandmother's toaster!
Nothing special, I've turned off both first party and third party cookies in the browser settings. I add sites I use regularly to a domain whitelist [1], other than that I don't use much anything for cookies anymore. Historically I also used to use Privacy Badger [2], but with my current setup it isn't really all that relevant.
In broader tracking and privacy context, I also use NextDNS [3] and uBlock Origin [4].
You could give uMatrix a try, I use it to block cookies, images, media and iframes. If you really want to allow a site to use all that it is a bit easier to do (I think)
But either way it works fine. Everything is a first class 'thing' that has properties that can be parameterised. If the 'thing' is an object the properties are alive and can be mutated (if mutable) for the lifetime of the object. If the 'thing' is a function the properties (arguments and local vars) are alive for the duration of the call.
When first class functions became mainstream though, I couldn't really understand the idea that they were fundamentally new compared to objects (in fairness this was only put forth by a few people)
My reasoning is based on two things: Firstly, objects have identity and this is a superfluous notion for most cases. Functions just "do things" to their inputs and return the result. Secondly, OOP is fundamentally predicated on the notion of mutation[0], but I think that is totally antithetical to local reasoning (chunking). The latter may just be a preference, but IME it seems to bear out. Mutation seems to be a consequence of the focus on identity, now that I think about it after writing that footnote.
> I couldn't really understand the idea that they were fundamentally new compared to objects (in fairness this was only put forth by a few people)
Just a historical note: First-class functions were invented waaaaay before OOP was a gleam in somebody's eye.
[0] The only non-mutating OOP system I know of is O'Camls version of OOP, but it's not very popular because it's just... unnecessary. It also does away with "identity", now that I think about it.
Coming from a ClojureScript background, it pains me to have to deal with all the idioms in React that hack around this fundamental impedance mismatch by forcing users to carefully maintain reference equality for collections between renders for perf optimizations, and more recently for correctness in hooks like useEffect.
But comparing modern Javascript to ancient PHP would be even more unreasonable.
When I write these sometimes-overly-simplified-articles I'm terrified of explaining something wrong, or that my knowledge may be incorrect on a very specific point.
I really like the way you express "everything is a first class 'thing'", I never thought of it that way.
"As I read articles and tried stuff out (and broke my server config more than once) I started to grasp the system, to get glimpses into how it all worked, until eventually it “clicked” and I felt comfortable working with it."
Have you written about your mental models for DNS? Color me interested!
Out of all the questions my students ask me, there's always confusion about the way servers talk to each other, or what they even are.
I'll definitely write about this now haha. Thank you for the idea :)
However, there is the concept of a pure function (one that is unaffected by and causes no side effects) which I think can be argued is fundamentally different.
[0] https://obedparla.com/code/a-visual-guide-to-react-mental-mo...
[1] The `setSearchValue` updates a value that's outside (and captured) by the closure, React will re-render the component since setSearchValue was called (idk how this works behind the scenes and i think rendering is async), during the re-render `searchValue` and `setSearchValue` are assigned values again, and thus `searchValue` will finally be the updated value.
Great attention to detail, thank you for pointing it out, I'll update it!
In Svelte you never think about these or other considerations (eg: useMemo). Just update some state and that's it. Svelte will render consistently faster than React, with less code to write (and maintain).
It's true with React you get a bit more flexibility but I have yet to find a situation that cannot be solved with Svelte.
Most of it can be explained in a HN comment:
Component state just does its thing. You change it with = and the component is re-rendered.
Writable stores need to trigger changes with set() and you need to use $ whenever you reference it in a .svelte file so that Svelte handles subscription automatically.
Reactive statements $: are a bit more complex. Every variable inside those is referenced by the compiler so that it knows when you are mutating it. Either with = or if it's a writable store with set().
An aside, and I know complaints about hooks are very boring now, but I remember when React first arrived on the scene. The API was a delight. shouldComponentUpdate, willReceiveProps, etc. Everything was so clear from the first second you picked it up. Now? I’m less sure.
I prefer the functional programming approach of React, but I love the performance and some of the developer ergonomics of Svelte.
Thank you to whoever uploaded it!
I'm happy to answer any questions
I think the problem with your visuals is the lack of depth in the colors. Especially because of the gradients, all the parts kind of blend together, so there’s no clear distinction between a parent component and its children for example. I think having clear distinct colored boxes with borders would help.
But you're right, I'm not satisfied yet with how the boxes show, I'll try to improve them for part 3 (and other articles that'll use them).
I spend a lot of time with these articles to have them as clear as possible.
I'll write many more such guides!
The only thing is that a lot of the same ideas are repeating themselves, but the specifics are worth explaining.
What could be done is to have the component function return a closure that returns the VDOM nodes instead of the VDOM nodes themselves, and then the hooks would be run only once, since rerendering would only run the returned closure and not the whole component function, and there would be no need for the magic or rules.
Like this:
Where the changes are that the hook takes the component as an argument, show is now a function rather than a value and a closure (that takes the props) is returned rather than returning the VDOM nodes directly.The React Hooks API is essentially equivalent to this, but inelegant, unintuitive and somewhat inefficient.
Just make the component itself stateful and the lifecycle explicit and it's all so much simpler. Then attack the problem of composing JS classes from pieces with tools available to all JS classes, not just the framework at hand.
You could have a stateful `this.query` object in your class, but you basically have to spread the code for that around (calling mount / unmount handlers or events) vs. just mentioning it in one place and having it be able to use more hooks internally etc.
It's basically like an `import` statement for your component.
Your query example is easily handled by a mixin:
You can also use helper objects: I understand that you have to wire up the lifecycle methods this way, but it's at least low-magic and easily understandable. I don't mind it.And even though decorators aren't standard yet, they can be used for composition too.
My point is that these problems aren't unique to React and deserve patters and solutions for all JavaScript programming. React is statefull and object-based under the hood, but just trying to hide it.
However, there is a major value hooks provide over class components and that's the composability of the lifecycle effects. The problem with class components is that code for a lifecycle is broken across multiple functions and interweaved with other effects. This happens because the declarative model of the component tree clashes with the imperative ordering we code up for lifecycle functions attached 'within' each class component. The fix is to turn lifecycle effects into first level declarative concepts themselves (like in hooks) so they're not 'within' the component, instead they're more of a has-a relationship.
This would actually be rather simple to do, drop the lifecycle methods in a component, and add a new Component.addEffect function (or, cause I'm a big fan of decorators, you could have some decorator to declare what effects should be used). And each effect would be another class that inherits from Effect. I think the hardest part to think about is how you interweave the effects like people do in hooks as one output becomes the input for another, but I think this is probably the wrong way to think about it. Instead if you think of it as an Effect tree then each Effect is just a 'render' function where you're passing in other effects as the props and the output is passed down the tree, so you'd just need an inline effect function that would compose 2 effects together the same way we do with components. At this point the conceptual model is extremely easy cause it's declarative trees all the way down!
Anyone wanna pass this idea onto the react team that'd be awesome cause I'd love to see this added for Class components to make them more composable as well as having better sharing of ideas between hooks and class components :)what could be done is actually using a proper reactive language where things look like this, instead of hacking HTML, JS and whatnot in an unholy mess of syntax:
e.g. a bit like this: https://tinyurl.com/yaawdomhhttps://obedparla.com/static/1c6caf6685f0bd3381d84a8166579de...
What's with the wavy lines as representation?
Similarly for an image titled 'Javascript closures visualized', we get https://obedparla.com/static/e0caedb67992235db77271808958da3...
This is just wrong. For example:
const counter = () => { let count = 1; return () => count++ }
(see also: the JS pattern of using closures to create objects with truly private "fields")
State lives in a "global" and the component is modifying it. So it's "leaking info out".
It's hard to make comprehensive mental models that cover all the bases and leaves nothing out
The state (of the hooks) for individual components are still scoped to that component only, its just stored in the "outer-most box", and the structure corresponds to the component tree. If a component unmounts and remounts, the prior state is lost.
When retrospecting about it, I realized I didn't think of it this way, even though it's correct. My mental model is like "state is just kept, don't worry about it".
Thank you for pointing it out though, it's hard to be accurate and succinct
This would still remove the visitors ability to say "I want THIS SITE to be in light/dark mode", without affecting all other sites / apps on their system. This page could support reading from the browser/OS, while still giving the choice on top of that.
Again, this choice was probably implemented ... to give the user a choice.
Some people may not want everything to be light or dark mode. I am one of them.
Some articles may not work well with dark mode.
Like someone above said, some sites implement it as an after-thought. In my case, I'm giving more love to dark-more because there are simply more nice color combinations in dark more than with white.
https://codepen.io/demilad/pen/bZRjpb
I don't think the commenter was being rude either, though it's obvious they're frustrated (understandably).
You can choose to cooperate with others or fight them. Usually when we cooperate, things are improved.
It was also unexpected. I didn't even know localStorage could be broken by disabling cookies haha.
It reminds me of the meme where a guy is riding his bike, inserts a stick into his own wheel while riding, falls over (understandably), and then blames someone else for his fall.
This breaks some video conferencing apps. It's a choice I have to make - some broken apps or a bunch of annoying websites.
Some websites use my cookies and localstorage to track me across properties. They do this so they can harvest my profile data and habits and sell them for profit without my permission.
Again, it's a choice and I think it's a fair one to make.
As engineers, we don't exist to punish our users for making choices different from ours. That's a great way to end up without any users. We should make their experience as good as we can and give them value. Sometimes that isn't possible - I can't make a video conference app that's immune to blocking autoplay. That doesn't mean we shouldn't try.
In my case, I was aghast my blog didn't work without cookies. I don't even have ads running on it, and it's static, it should work on my grandmother's toaster!
In broader tracking and privacy context, I also use NextDNS [3] and uBlock Origin [4].
[1] chrome://settings/content/cookies?search=cookies
[2] https://privacybadger.org/
[3] https://nextdns.io/
[4] https://github.com/gorhill/uBlock